Compare commits

...

314 Commits

Author SHA1 Message Date
Andrew Kushnir fae3c837e2 release: cut the v18.2.9 release 2024-10-23 13:11:42 -07:00
Jessica Janiuk 47270d551b ci: clean up pullapprove list (#58325)
This removes no longer active users from the shared primitives list of reviewers.

PR Close #58325
2024-10-23 07:59:51 -07:00
Jessica Janiuk 2fb88a56dd ci: temporarily disable adev tests (#58309)
This disables adev tests until the next release. This is due to #54858.

PR Close #58309
2024-10-22 11:30:38 -07:00
Angular Robot 713a14fadd build: update actions/cache digest to 6849a64 (#58305)
See associated pull request for more information.

PR Close #58305
2024-10-22 09:44:22 -07:00
Matthieu Riegler 7e933d8486 docs(docs-infra): fix CLI commands (#58294)
With this commit the CLI doc will display the correct arguments for the subcommands

fixes #58279

PR Close #58294
2024-10-22 07:42:22 -07:00
Angular Robot 45c24e4aaf build: update io_bazel_rules_sass digest to 6e23fb1 (#58270)
See associated pull request for more information.

PR Close #58270
2024-10-19 17:44:59 +02:00
Todd Palmer ac6e977663 docs: fix typo in tutorial - change style.css to styles.css to correct style sheet file name (#58269)
PR Close #58269
2024-10-19 17:43:20 +02:00
Daniel Kimmich b63c2edd76 docs: update default asset paths (#58246)
PR Close #58246
2024-10-18 14:35:31 +00:00
TupiC d1cfbb1f92 docs: removed typos of withHttpTransferCacheOptions description (#58244)
Removed two typos in the withHttpTransferCacheOptions function description.

- Corrected spelling of "whether"
- Changed spelling from "a an" to "an"
PR Close #58244
2024-10-18 10:14:10 +00:00
Angular Robot b169eb11db build: update io_bazel_rules_sass digest to 43a9cda (#58253)
See associated pull request for more information.

PR Close #58253
2024-10-18 10:04:45 +00:00
Kristiyan Kostadinov b0ab653965 fix(compiler-cli): report when NgModule imports or exports itself (#58231)
Reports a diagnostic if an NgModule refers to itself in its `imports` or `exports`. Previously the compiler would go into an infinite loop.

Fixes #58224.

PR Close #58231
2024-10-18 09:26:45 +00:00
wlotherington 98c9ac8f92 docs(docs-infra): fix playground terminal/console (#58257)
xterm was not resizing correctly when terminal/console component was resized.
- Load fitAddon for each terminal instance to enable resize.
- Use `ResizeObserver` to detect changes to the component size changes (window resize does not catch `as-split` events).
- Move padding from `.xterm-screen` to `.xterm` ancestor to fix text alignment.

Fixes #52645

PR Close #58257
2024-10-18 09:25:36 +00:00
cexbrayat 37b921dcde refactor(core): test EventEmitter completion on destroy with outputToObservable (#58239)
A unit test has been added to check that `EventEmitter` properly completes upon component/directive destrouction when used with `outputToObservable`.
It explains why `destroyRef` has to be injected in `EventEmitter` in the first place.

PR Close #58239
2024-10-17 11:37:58 +00:00
Angular Robot d330f94e48 build: update io_bazel_rules_sass digest to 9fa2d50 (#58235)
See associated pull request for more information.

PR Close #58235
2024-10-17 10:13:58 +00:00
wlotherington e60d016f46 docs(docs-infra): change mermaid diagram colors (#58223)
Change diagram colors to:

- Better match the overall site theme.
- Improve dark theme.
- Make the diagram easier to understand. "Unchecked" nodes have no color and "Checked" nodes match the color of the "Event" node that triggered the change detection.

Fixes #56314

PR Close #58223
2024-10-16 16:46:17 +00:00
Dzhavat Ushev 532bd11f41 docs(language-service): fix link URLs (#58143)
PR Close #58143
2024-10-16 14:48:13 +00:00
Matthieu Riegler 608a1511ca refactor(compiler-cli): Don't extract abstract overload multiple times (#57707)
Prior to this commit, each abstract method that was overloaded was extracted. With this commit it will be extracted only once. Every overload was and still will be supported by the signatures.

fixes #57693

PR Close #57707
2024-10-16 07:31:48 +00:00
Alan Agius 06f45c3d59 docs: update browserslist config to include last 2 Android major versions (#58186)
See: https://github.com/angular/angular-cli/pull/28620 for more context.

PR Close #58186
2024-10-15 16:48:59 +00:00
Angular Robot 4049d7797d build: update scorecard action dependencies (#58202)
See associated pull request for more information.

PR Close #58202
2024-10-15 06:38:07 +00:00
Daniel Kimmich c1f4a47457 docs: remove word repetition (#58187)
PR Close #58187
2024-10-14 13:19:42 +00:00
Paul Gschwendtner 6cd2028d81 ci: disable bazel-saucelabs job (#58162)
Disables the `bazel-saucelabs` job in `main`, and re-uses the PR legacy
saucelabs job to run on `main` instead (which is much more stable and
reliable).

PR Close #58162
2024-10-11 14:07:35 +00:00
Angular Robot d9bb74f950 build: update io_bazel_rules_sass digest to 583fc8c (#58155)
See associated pull request for more information.

PR Close #58155
2024-10-11 10:37:57 +00:00
vladboisa 47467949d8 docs: move JSDoc below constructor (#57187)
Move the JSDoc above transform signature for correct view

Fixes #56349

refactor: format file

PR Close #57187
2024-10-10 12:37:36 +00:00
Steven Tucker 00de13c909 docs: Updated event-listeners.md doc to use correct term, even -> every (#57964)
event time -> every time

PR Close #57964
2024-10-10 12:36:41 +00:00
arturovt 79a89f7742 refactor(docs-infra): allow table of contents to be GCed (#58034)
This commit updates the table of contents functionality to clean up correctly whenever the user
navigates to other pages and nodes are removed from the DOM.

Currently, calling `renderComponent` with the `TableOfContents` keeps creating a new table of contents
component without removing the previous one, as they are created manually.

This leads to memory leaks because the components cannot be collected properly, even if the user
navigates to the home page where there is no TOC component.

PR Close #58034
2024-10-10 12:33:51 +00:00
Matthieu Riegler a9e440d96b refactor(core): drop the Mutable utility type. (#58124)
The complexity of this type isn't necessary, `Writable` is well suited where it was used.

PR Close #58124
2024-10-10 10:47:51 +00:00
reosablo da0e6b1eee docs(core): fix description about signal value equality (#56472)
PR Close #56472
2024-10-10 10:24:33 +00:00
Jaime González Fábregas 45a788b0b7 docs(docs-infra): substitute string interpolation on json data with actual value (#57626)
substitute "${this.baseUrl}" with "https://angular.dev/assets/images/tutorials/common" in the photo url on the json data provided on the hands on tutorial in the webpage "https://angular.dev/tutorials/first-app/14-http". The previous json content caused the images to fail loading.

PR Close #57626
2024-10-10 10:22:34 +00:00
Paul Gschwendtner 2532ffa35e release: cut the v18.2.8 release 2024-10-10 11:59:47 +02:00
Georgy Serga 21be258be6 fix(compiler): scope :host-context inside pseudo selectors, do not decrease specificity (#57796)
parse constructions like `:where(:host-context(.foo))` correctly
revert logic which lead to decreased specificity if `:where` was applied
to another selector, for example `div` is transformed to `div[contenta]`
with specificity of (0,1,1) so `div:where(.foo)` should not decrease it
leading to `div[contenta]:where(.foo)` with the same specificity (0,1,1)
instead of `div:where(.foo[contenta])` with specificity equal to (0,0,1)

PR Close #57796
2024-10-10 08:13:23 +00:00
Georgy Serga d325f9b55f fix(compiler): fix parsing of the :host-context with pseudo selectors (#57796)
fix regexp which is used to test for host inside pseudo selectors

PR Close #57796
2024-10-10 08:13:23 +00:00
Georgy Serga aea747ab3b fix(compiler): preserve attributes attached to :host selector (#57796)
keep attributes used to scope :host selectors

PR Close #57796
2024-10-10 08:13:23 +00:00
Georgy Serga 11692c8dab fix(compiler): add multiple :host and nested selectors support (#57796)
add support for nested and deeply nested (up to three levels) selectors,
parse multiple :host selectors, scope selectors within pseudo functions

PR Close #57796
2024-10-10 08:13:23 +00:00
Georgy Serga 48a1437e77 fix(compiler): fix comment typo (#57796)
fix spelling in the comment

PR Close #57796
2024-10-10 08:13:23 +00:00
Georgy Serga 66dcc691f5 fix(compiler): allow combinators inside pseudo selectors (#57796)
allow css combinators within pseudo selector functions, parsing those
correctly. Similarly to previous version, don't break selectors
into part if combinators are within parenthesis, for example
`:where(.one > .two)`

PR Close #57796
2024-10-10 08:13:23 +00:00
Georgy Serga 7a6fd427d5 fix(compiler): transform pseudo selectors correctly for the encapsulated view (#57796)
fix scoping and transforming logic of the `shimCssText` for the
components with encapsulated view:
- add support for pseudo selector functions
- apply content scoping for inner selectors of `:is()` and `:where()`
- allow multiple comma separated selectors inside pseudo selectors

Fixes #45686

PR Close #57796
2024-10-10 08:13:22 +00:00
cexbrayat 31fb9d0d52 docs: mention autoDetectChanges parameter default value (#58092)
It was unclear whether the parameter was necessary, as its default value was not mentioned in the jdsoc.

PR Close #58092
2024-10-09 13:50:32 +00:00
Krzysztof Platis b40875a2cc fix(platform-server): destroy PlatformRef when error happens during the bootstrap() phase (#58112) (#58135)
The `bootstrap()` phase might fail e.g. due to an rejected promise in some `APP_INIIALIZER`.
If `PlatformRef` is not destroyed, then the main app's injector is not destroyed and therefore `ngOnDestroy` hooks of singleton services is not called on the end (failure) of SSR.

This could lead to possible memory leaks in custom SSR apps, if their singleton services' `ngOnDestroy` hooks contained an important teardown logic (e.g. unsubscribing from RxJS observable).

Note: I needed to fix by the way another thing too: now we destroy `moduleRef` when `platformInjector` is destroyed - by setting a `PLATFORM_DESTROY_LISTENER`

Patch port of #58112

PR Close #58135
2024-10-09 13:50:02 +00:00
Angular Robot 59394ee830 build: update actions/cache digest to 3624ceb (#58125)
See associated pull request for more information.

PR Close #58125
2024-10-08 13:27:59 -07:00
Kristiyan Kostadinov 46bafb0b0a fix(core): clean up afterRender after it is executed (#58119)
We stop tracking `afterRender` hooks as soon as they execute, but their on destroy callbacks stay registered until either the injector is destroyed or the user calls `destroy` manually. This was leading to memory leaks in the `@defer` triggers based on top of `afterRender` when placed inside long-lived views, because the callback would execute, but its destroy logic was waiting for the view to be destroyed.

These changes resolve the issue by destroying the `AfterRenderRef` once it is executed.

PR Close #58119
2024-10-08 13:27:07 -07:00
Matthieu Riegler 9ca9db4f74 refactor(core): drop ViewRefTracker in favor of ApplicationRef. (#58096)
We can leverage an `import type` to prevent the circular import.

PR Close #58096
2024-10-08 13:25:24 -07:00
ColinJolivet 9f2726bb43 refactor(docs-infra): add tooltip to the download button in playground (#58065)
Add a material tooltip to the download button in the playground in order to clarify what this button does

PR Close #58065
2024-10-08 09:28:37 -07:00
Lang 490786af10 docs: complete the example in use InjectionToken section (#57839)
PR Close #57839
2024-10-08 09:27:23 -07:00
Angular Robot 7b13d34a24 build: update scorecard action dependencies (#58116)
See associated pull request for more information.

PR Close #58116
2024-10-08 06:18:16 +00:00
Angular Robot 99b5417d28 build: update actions/cache digest to 2cdf405 (#58107)
See associated pull request for more information.

PR Close #58107
2024-10-08 06:17:36 +00:00
Angular Robot bf83801b8c build: update actions/checkout digest to eef6144 (#58108)
See associated pull request for more information.

PR Close #58108
2024-10-07 13:26:53 -07:00
arturovt 02de7fc731 fix(zone.js): remove abort listener once fetch is settled (#57882)
This commit updates the `fetch` patch for zone.js. Currently, we're attaching an
`abort` event listener on the signal (when it's provided) and never removing it.
We should be good citizens and remove event listeners whenever objects need to be
properly collected. In Firefox, when saving a heap snapshot and running it through
`fxsnapshot`, querying `AbortSignal` will print a so-called "CaptureMap" with a list
of "lambdas," indicating that the signal is not garbage collected because of the event
listener lambda function.

PR Close #57882
2024-10-07 08:27:54 -07:00
Kristiyan Kostadinov f187c3abf8 fix(compiler-cli): defer symbols only used in types (#58104)
Currently we don't defer any symbols that have references outside of the `import` statement and the `imports` array. This is a bit too aggressive, because it's possible that the symbol is only used for types (e.g. `viewChild<SomeCmp>('ref')`) which will be stripped when emitting to JS.

These changes expand the logic so that references inside type nodes aren't considered.

**Note:** one special case is when the symbol used in constructor-based DI (e.g. `constructor(someCmp: SomeCmp)`, because these constructors will be compiled to `directiveInject` calls. We don't need to worry about them, because the compiler introduces an addition `import * as i1 from './some-cmp';` import that it uses to refer to the symbol.

Fixes #55991.

PR Close #58104
2024-10-07 08:26:07 -07:00
Luan Gong 7eb264a017 docs: use correct heading in templates guide (#58101)
PR Close #58101
2024-10-07 08:22:46 -07:00
Matthieu Riegler b35e7f8bb3 docs: add info on AbstractControl.source type. (#58094)
The source can be of any type and can't be inferred from `T`

fixes #58076

PR Close #58094
2024-10-07 08:21:56 -07:00
vladboisa 10081a9d7a docs: move JSDoc before functions (#58087)
Move the JSDoc before functions for correct view of params

PR Close #58087
2024-10-04 14:47:53 -07:00
Matthieu Riegler 0e225ce16b docs(docs-infra): Add NgModule exports for directives. (#58071)
This information is extracted from the @NgModule Jsdoc tag.

fixes #57906

PR Close #58071
2024-10-04 14:46:17 -07:00
Adrien Crivelli ccf2c7f40e docs(common): Minor typo in code example (#58085)
PR Close #58085
2024-10-04 14:38:41 -07:00
JoostK 39a851fa55 refactor: change security issue redirect to angular.dev (#58070)
Ensure the security issue template points to the correct location.

PR Close #58070
2024-10-04 10:57:43 -07:00
Stanka Kopalova 91874a7c41 docs: add better title name (#58060)
PR Close #58060
2024-10-04 10:57:08 -07:00
Stanka Kopalova 3b06a09df2 docs: fix wrong title of section (#58060)
PR Close #58060
2024-10-04 10:57:08 -07:00
Angular Robot 3fb696df96 refactor(compiler): update dependency chokidar to v4 (#57945)
This commit bump chokidar to the latest major and adjusts to the breaking changes.

PR Close #57945
2024-10-04 10:56:03 -07:00
arturovt 69702b17cc docs(zone.js): update docs to enable beforeunload (#57881)
In this commit, we update the documentation to reflect the property that allows
enabling the default browser `beforeunload` handling, which was added in a
previous commit.

Additionally, some cosmetic grammar changes have been made in this documentation,
as the previous text had some issues.

Closes #52256

PR Close #57881
2024-10-04 10:55:01 -07:00
Matthieu Riegler de03e93262 build: update ts target to ES2022 and module to ES2022 (#58022)
Our integration tests are based on the CLI. CLI build force the target to ES2022 else it logs a warning

PR Close #58022
2024-10-04 13:33:48 +00:00
Jeevan Mahesha 44cb2f8131 docs: add documentation for lazy loading a standalone component (#57620)
Added a new section in the documentation explaining how to lazy load a standalone component using `loadComponent`. This includes a code example demonstrating the setup in Angular routes.

PR Close #57620
2024-10-04 13:31:52 +00:00
Matthieu Riegler 1db1b3d6b8 refactor(compiler-cli): exclude private computed properties from class member extractions (#57596)
This will exclude properties like `[ɵWRITABLE_SIGNAL]` from the `WritableSignal` interface.

PR Close #57596
2024-10-04 13:29:52 +00:00
Matthieu Riegler c18ec3dd26 ci: disable updates for @bazel (#58050)
This is to prevent the bot to update #51047

PR Close #58050
2024-10-04 13:28:16 +00:00
vladboisa 694fe3faab fix(docs-infra): fix calculation of height editor (#54128)
Apply the min() function for set the smallest height

fix(docs-infra): move height into editor-wrapper

Move the calculation rule of height edit into editor-wrapper selector

fix(docs-infra): change has selector

Change the has selector

fix(docs-infra): change selector's for child

Changing the selector for test this solution

Fix

PR Close #54128
2024-10-03 16:43:03 +00:00
vladboisa ca5bc0c5bd feat(docs-infra): add transition on editor wrapper (#54128)
Add the smooth animation when height of the container is changing

PR Close #54128
2024-10-03 16:43:03 +00:00
vladboisa 3d1fd3442e fix(docs-infra): max-height IDE error panel visibility (#54128)
Remove max-height: 200px in ul child inline-errors-box, add the overflow & max-height in percentages to the parent for correct visualization

Fixes #52760

refactor(docs-infra): correct typo
Correct typo in comment

feat(docs-infra): modify the height of the editor
If error box are displayed, modify the height of the editor

PR Close #54128
2024-10-03 16:43:03 +00:00
Andrew Seguin 8f6560857c docs: reword when to use model inputs (#57648)
PR Close #57648
2024-10-03 16:42:18 +00:00
arturovt dc911c635a refactor(docs-infra): cleanup AfterRenderSequence for reference list (#58030)
In this commit, we're replacing the provided injector in `afterNextRender` with a node injector
because it was previously mistakenly passing an `EnvironmentInjector`. The `EnvironmentInjector`
resolves `DestroyRef` to itself, meaning that `AfterRenderSequence` is essentially never destroyed
(since the environment injector is not destroyed either).

PR Close #58030
2024-10-02 18:01:31 +00:00
Jeevan Mahesha 42eb096b77 docs: update comments to use consistent code formatting for boolean values (#57619)
PR Close #57619
2024-10-02 18:00:52 +00:00
Paul Gschwendtner 46b088a245 release: cut the v18.2.7 release 2024-10-02 14:31:40 +02:00
arturovt 46a2ad39f5 fix(common): prevent warning about oversize image twice (#58021)
I’ve noticed that there was a loop inside a loop. Since we’re already iterating through
`images` using `forEach`, it was running a `for` loop through `images` again. This was
probably a mistake made when the functionality was initially added. The test actually
verified that `logs.length` is `1`, but in the real environment, it logs twice
(which is quite obvious due to the code).

I’ve also added the missing file to the Bazel target.

PR Close #58021
2024-10-02 11:46:18 +00:00
Suat Oğulcan Özcan c48816e123 docs: remove whitespace (#58042)
PR Close #58042
2024-10-02 11:26:28 +00:00
Matthieu Riegler 7c16e70b20 build: Add LICENSE file to the generated packages (#58033)
This commit adds a parameter to `ng_package` to specify a license file that will be bundled with the generated packages.

fixes #58029

PR Close #58033
2024-10-01 16:34:27 +00:00
Sam Verschueren 70591e7de0 docs: use proper workdir for tutorial and playground (#56063)
PR Close #56063
2024-10-01 16:09:54 +00:00
arturovt a21867df6a fix(docs-infra): allow tutorial component to be GCed (#58028)
This commit updates the tutorial component's functionality to unsubscribe from the route
data observable and stop monitoring the resizer element when the component is destroyed.
This is necessary because `monitor` adds event listeners that prevent the element and
component from being garbage collected.

PR Close #58028
2024-10-01 13:51:06 +00:00
ilir.beqiri 8d8fb44ec1 docs: update the name of the sub-navigation item at error encyclopedia menu (#57997)
PR Close #57997
2024-10-01 08:32:09 +00:00
Angular Robot 6b67cb5976 build: update scorecard action dependencies (#58024)
See associated pull request for more information.

PR Close #58024
2024-10-01 08:25:10 +00:00
Andrew Scott 0e62b30989 docs(router): Document missing input for route binding (#57697)
This commit clearly documents the approach used by router input binding
when an input is not present in the route data.

resolves #52946

PR Close #57697
2024-10-01 08:09:33 +00:00
arturovt ca637fe6a9 fix(http): cleanup JSONP script listeners once loading completed (#57877)
This commit removes event listeners from the `script` element once loading is
complete. If the element is not garbage collected properly, in Firefox, the script
element still appears in the memory tree view, captured by
`__zone_symbol__loadfalse -> HTMLScriptElement -> GC Roots`. We should always be good
citizens and clean up event listeners when we no longer need them, as browser's garbage
collectors work differently. Calling `remove()` on the node doesn't guarantee that the
node can be collected.

PR Close #57877
2024-10-01 08:08:40 +00:00
Thanh Truong b84e2d3338 docs: add deferrable views tutorial (#57934)
Add a new tutorial "Deferrable Views" to the tutorials page.

PR Close #57934
2024-10-01 07:07:21 +00:00
Kristiyan Kostadinov b9d846dad7 fix(migrations): delete constructor if it only has super call (#58013)
Adds some logic to the `inject` migration that will remove constructors that are made up of only a `super` call after the migration.

PR Close #58013
2024-09-30 13:39:43 -07:00
Santosh Yadav 8bb21de452 docs: correct line number for localize example (#57999)
PR Close #57999
2024-09-30 13:39:01 -07:00
Angular Robot bf4215356e build: update io_bazel_rules_sass digest to 7bceee9 (#57998)
See associated pull request for more information.

PR Close #57998
2024-09-30 13:36:37 -07:00
Andrew Scott b3ebbf48a3 ci: add atscott to unavailable list (#57995)
I will not be available to review PRs for a while

PR Close #57995
2024-09-30 13:34:24 -07:00
Aristeidis Bampakos 7cbec22a85 docs(docs-infra): use angular.dev in the pull request template (#57990)
PR Close #57990
2024-09-30 13:33:00 -07:00
Aristeidis Bampakos e3adaeaed3 docs(docs-infra): link issue template in the new docs (#57989)
PR Close #57989
2024-09-30 13:32:21 -07:00
arturovt 8f2b0ede59 fix(common): skip checking whether SVGs are oversized (#57966)
Prior to this commit, the `ImagePerformanceWarning` class was checking all `img`
elements in the DOM to determine whether they were oversized after the DOM loading
was complete. We should not check SVGs because they are vector-based and can scale
infinitely without losing quality.

Closes #57941

PR Close #57966
2024-09-30 13:28:46 -07:00
mgechev add5c256bc docs: set expiry date to the developer survey (#57981)
Close the developer survey on September 27, 2024

PR Close #57981
2024-09-30 12:51:28 -07:00
mgechev d0612d6c6d docs: add expiry to the top banner (#57981)
Adding an expiry date to the banner similarly to
aio so we can automatically hide it when we reach
a certain date.

PR Close #57981
2024-09-30 12:51:28 -07:00
Joey Perrott 69b7cf54e0 build: update @angular/build-tooling to latest version (#57980)
Update @angular/build-tooling to the latest version

PR Close #57980
2024-09-26 14:31:38 -07:00
Alan Agius 4875bc3073 fix(docs-infra): update getAnswerFiles to ensure compatibility with non-POSIX file systems (#57970)
This update modifies the `getAnswerFiles` function to support file systems that do not adhere to POSIX standards.

Prior to this change this method will always fail with the below:

```
Error: Invalid state: could not find start of answers path
```

PR Close #57970
2024-09-26 14:29:45 -07:00
Kristiyan Kostadinov 2f347ef8fc fix(core): provide flag to opt into manual cleanup for after render hooks (#57917)
Adds a `manualCleanup` flag to `afterRender` and `afterNextRender`, similarly to `effect`. The reason is that currently if the hook is created outside of an injection context, it requires an injector to be passed in. In some cases that injector might be an injector that is never destroyed (e.g. `EnvironmentInjector`) which can give a false sense of security users thinking that the hook will be cleaned up automatically. We fell into this in the CDK which caused a memory leak (see https://github.com/angular/components/pull/29709). With the `manualCleanup` option users explicitly opt into cleaning the hook up themselves.

PR Close #57917
2024-09-26 14:20:55 -07:00
Swami 9d5b1eccff docs: correct HashLocationStrategy example url (#57908)
PR Close #57908
2024-09-26 14:20:01 -07:00
Thomas Nguyen 2e7cfcb2ef refactor(core): Remove global event delegation code. (#57893)
This is no longer needed since we are no longer experimenting with it.

PR Close #57893
2024-09-26 14:18:18 -07:00
Alex Rickabaugh e40a4fa3c7 fix(upgrade): support input signal bindings (#57020)
`@angular/upgrade` writes to inputs when downgrading an Angular 2+ component
into an Angular.JS adapter. Previously, it wrote directly to the input
property, which isn't compatible with input signals. It also handles
`ngOnChanges` directly.

The correct way to support input signals would be to refactor upgrade to use
`ComponentRef.setInput`, which also handles `ngOnChanges` internally.
However, this refactoring might be more breaking since it would change the
timing of certain operations. Instead, this commit updates the code to
recognize `InputSignal` and write it through the `InputSignalNode`. This
avoids the above breaking changes for now, until a bigger refactoring can be
tested.

Fixes #56860.

PR Close #57020
2024-09-26 14:14:15 -07:00
Enea Jahollari 901c1e1a7f fix(compiler-cli): correctly get the type of nested function call expressions (#57010)
This PR fixes a bug where the type of a nested function call expression was incorrectly being returned as null.

PR Close #57010
2024-09-26 14:13:04 -07:00
arturovt 249d0260f9 fix(common): execute checks and remove placeholder when image is already loaded (#55444)
With this commit, we're now able to perform checks even when the image has already
been loaded (e.g., from the browser cache), and its `load` event would never be triggered.
We use the [complete](https://html.spec.whatwg.org/#dom-img-complete) property, as specified,
which indicates that the image state is fully available when the user agent has retrieved all
the image data. This approach effectively triggers checks, as we no longer solely rely on the
`load` event and consider that the image may already be loaded.

This will not remove the placeholder until the `load` event fires (and it won't fire if the
image is already "there").

This prevents memory leaks in development mode, as `load` and `error` event listeners are
still attached to the image element.

PR Close #55444
2024-09-26 14:12:01 -07:00
Andrew Scott 1391928e0a release: cut the v18.2.6 release 2024-09-25 10:00:09 -07:00
Joey Perrott dbf15c8f34 fix(docs-infra): extend the timeout for jasmine tests of mermaid (#57948)
Extend the timeout because mermaid takes too long for the default.

PR Close #57948
2024-09-24 13:02:53 -07:00
Joey Perrott 03ac3c299d refactor: update license text to point to angular.dev (#57902)
Update license text to point to angular.dev instead of angular.io

PR Close #57902
2024-09-24 15:28:46 +02:00
Arshjeet2003 8f6c326fa6 docs: fix image src in feature modules (#57607)
fix image src in feature modules at /guide/ngmodules/feature-modules

PR Close #57607
2024-09-24 15:01:11 +02:00
Grégoire FOUILLARD 9db9213687 docs: use X link instead of twitter (#57608)
PR Close #57608
2024-09-24 14:47:02 +02:00
Johann Ulbrich 95b523c7e9 docs: Correct missing validator and wrong input field name in tutorial (#57738)
PR Close #57738
2024-09-24 14:46:05 +02:00
ilyapol 11795f222f docs: update function-based outputs names (#57930)
According to Choosing event names for Outputs and this line
`Always use camelCase output names. Avoid prefixing output names with "on".`
So I made updates for Function-based outputs chapter in all examples from onNameChange functions to nameChange function

PR Close #57930
2024-09-24 14:45:11 +02:00
Andrew Kushnir 97e28411fa refactor(core): avoid producing zone-related warnings during hydration when in zoneless mode (#57911)
This commit updates hydration code to avoid logging "unsupported configuration" warnings when in zoneless mode.

PR Close #57911
2024-09-24 12:52:41 +02:00
Andrew Scott b7b0052d48 refactor(router): Move view transitions to developer preview (#55600)
Nothing of concern arose during the time this feature was in the
experimental phase. There are no plans to change the shape of the API.

This change also updates the route animations documentation to use the
view transitions feature instead of the old and difficult to follow
guide that used the animations package. The content was taken from the
blog post: https://blog.angular.io/check-out-angulars-support-for-the-view-transitions-api-3937376cfc19

PR Close #55600
2024-09-24 12:07:03 +02:00
Angular Robot 997fe96d3b build: update dependency @rollup/plugin-commonjs to v28 (#57929)
See associated pull request for more information.

PR Close #57929
2024-09-24 11:48:51 +02:00
Angular Robot 613dd02746 build: update github/codeql-action action to v3.26.8 (#57937)
See associated pull request for more information.

PR Close #57937
2024-09-24 11:12:41 +02:00
Alan Agius 1825460fc3 fix(docs-infra): resolve Mermaid from Bazel root path (#57924)
This update addresses a build failure on Windows caused by the previous method, which was incompatible with the Windows environment.

Closes #57920

PR Close #57924
2024-09-23 17:20:54 +02:00
AleksanderBodurri 3cc40f304d refactor(devtools): provide user with suggestions on why their angular application is not being detected in dev mode (#57861)
We've been seeing some reports about Angular DevTools being unable to detect applications running in dev mode.

This commit adds more context to the error message displayed when development mode is not detected and offers some possible resolutions.

Displays 3 common reasons why DevTools fails to detect an application running in dev mode. Links directly to angular.dev for relevant configurations.

Links to the Angular DevTools issue template if none of the suggestions work.

PR Close #57861
2024-09-23 16:08:17 +02:00
Tarique Akhtar Ansari bc0bc5716d docs: fix import name in in ng-content.md (#57836)
Corrects import symbol in
adev/src/content/guide/templates/ng-content.md

Closes #57836

PR Close #57836
2024-09-23 16:04:16 +02:00
Sheik Althaf 49c6a579d1 fix(devtools): remove existing highlight before highlighting another element (#57746)
In this PR we will remove the exiting highlight before highlighting the another element so that the highlight always be one

PR Close #57746
2024-09-23 16:00:24 +02:00
Tomer953 609201527f docs: add signal example for "Binding to the host element" section (#57272)
Added a new example demonstrating the use of signals in the "Binding to the host element" section.
PR Close #57272
2024-09-23 12:01:05 +02:00
ChinoUkaegbu c6b86357f6 docs(core): update wording in ComponentDecorator (#57878)
PR Close #57878
2024-09-23 11:59:08 +02:00
Angular Robot cd6ecdbcf8 build: update io_bazel_rules_sass digest to 1fc4bd9 (#57907)
See associated pull request for more information.

PR Close #57907
2024-09-23 11:42:34 +02:00
Alejandro Cuba Ruiz f3f4548033 docs: remove @empty <li> from the a11y tree (#54676)
PR Close #54676
2024-09-23 11:40:40 +02:00
Joey Perrott fc2f936e2f ci: update ownership to include MarkTechson (#57885)
Update pullapprove to include MarkTechson in ownership of a couple areas.

PR Close #57885
2024-09-20 16:59:50 +02:00
Enea Jahollari a694d3d7b8 docs: Update ng-container.md (#57770)
PR Close #57770
2024-09-20 16:57:39 +02:00
Ahmed Hakeem bca8026385 docs: include a second instance in which NG0300 err occurs (#56549)
PR Close #56549
2024-09-20 16:56:43 +02:00
Rahat Ahmed 157c30e3e0 docs(core): Fix typo in DI docs (#55563)
PR Close #55563
2024-09-20 16:54:45 +02:00
Tobi 1ee7ec5204 docs(http): heads up about provider ordering (#56518)
PR Close #56518
2024-09-20 15:29:16 +02:00
Angular Robot e0ead9212f build: update io_bazel_rules_sass digest to 8dac332 (#57892)
See associated pull request for more information.

PR Close #57892
2024-09-20 11:13:49 +02:00
Ulises e53f753ee1 docs: update installation guide (#57701)
PR Close #57701
2024-09-20 11:09:03 +02:00
Matthieu Riegler 07a485fdba docs(docs-infra): Add dedicated support for decorators. (#57595)
PR Close #57595
2024-09-19 14:16:33 -07:00
Ankur Mishra 88457ab9be docs: Update Making HTTP requests best practices example (#57876)
PR Close #57876
2024-09-19 14:18:29 +02:00
Luke Schlangen 47d5341fc5 docs: fix closing tag slash location (#57405)
PR Close #57405
2024-09-19 12:50:28 +02:00
Harieswaran D 54a0ea785f docs: suggesting a typo error in the document (#57627)
PR Close #57627
2024-09-19 12:49:52 +02:00
Charles Lyding ad9ba9a95f build(docs-infra): use chunk optimizer for adev builds (#56830)
The Angular build system recently introduced an opt-in chunk optimizer
for application builds. This is now enabled for adev production builds.
It reduces the initial chunk count from 14 to 1 JavaScript file. While
the raw initial total file size does increase by 0.75% (7.3kB), the total
estimated transfer size decreases by 8% (17.8kB). Not only does this
reduce the amount of data that must be sent over the network but it also
reduces the amount of HTTP requests that must be made by the browser.
While the injected HTML module preloads mitigate request cascades, not
needing to make the requests is even better.

PR Close #56830
2024-09-19 12:44:51 +02:00
Angular Robot 1701c40f08 build: update cross-repo angular dependencies (#57857)
See associated pull request for more information.

Closes #57833 as a pr takeover

PR Close #57857
2024-09-19 11:20:26 +02:00
Alan Agius 6f0042e4a1 test: disable cache and analytics (#57863)
In Bazel, the `CI` environment variable is not set due to its hermetic nature. As a result, caching is enabled by default, which includes Vite pre-bundling. This is unnecessary in CI environments.

In some cases, this leads to errors during CI runs when the Vite build is closed or canceled prematurely, resulting in the following errors:

```
Error: R] The build was canceled
Error: R] Terminating worker thread [plugin angular-vite-optimize-deps]
```

PR Close #57863
2024-09-19 11:14:23 +02:00
Angular Robot ac3fc9a794 build: update io_bazel_rules_sass digest to 1f2bb53 (#57860)
See associated pull request for more information.

PR Close #57860
2024-09-18 17:48:06 +02:00
Pawel Kozlowski 2f96a348be release: cut the v18.2.5 release 2024-09-18 17:29:45 +02:00
Matthieu Riegler 0365f38492 docs: adapt the NgModule to the new default : standalone (#57532)
PR Close #57532
2024-09-18 16:53:27 +02:00
Matthieu Riegler b5d70b86b5 docs: add redirect for pipes guide (#57706)
cherry-pick of #57681 that should have targeted `main`. This commit also fixes the wild card redirection

PR Close #57706
2024-09-18 16:51:45 +02:00
zdwg42 5d19174cba docs(forms): fix template-driven docs link (#57867)
PR Close #57867
2024-09-18 16:50:18 +02:00
Kristiyan Kostadinov e685ed883a fix(compiler-cli): extended diagnostics not validating ICUs (#57845)
The visitor that all extended diagnostics are based on hadn't implemented the `visitIcu` method which meant that it wasn't detecting any code inside of them.

Fixes #57838.

PR Close #57845
2024-09-18 09:48:21 +02:00
Joey Perrott 7fdb47956b build: update dependency @inquirer/prompts to v6 (#57830)
update dependency @inquirer/prompts to v6

PR Close #57830
2024-09-18 09:46:06 +02:00
Matthieu Riegler 76709d5d6e fix(core): Handle @let declaration with array when preparingForHydration (#57816)
Before this commit, `@let` decleration with an array where  mistaken for a component in the lView and throwing an unexpected error.

This commit fixes this.

PR Close #57816
2024-09-17 16:29:40 +02:00
Angular Robot dbd697ca2d build: update github/codeql-action action to v3.26.7 (#57843)
See associated pull request for more information.

PR Close #57843
2024-09-17 15:48:13 +02:00
Ingila Ejaz c0b49867f1 docs: add fork url in contribution guidelines for new contributors (#57837)
Clicking a clickable repo url in the contribution guide would make it simpler for new contributors to find the repository
PR Close #57837
2024-09-17 15:23:35 +02:00
Vlad Boisa cd970541fe docs: change spelling to correctly params (#57832)
PR Close #57832
2024-09-16 18:03:53 +02:00
Samson Amaugo e16b8a3794 docs: correct key usage in sectionStyles and update template binding (#57820)
PR Close #57820
2024-09-16 11:26:05 +02:00
Can Özkan 218999025a docs: escape pipe ("|") characters in markdown tables to fix rendering issues (#57822)
PR Close #57822
2024-09-16 11:24:55 +02:00
Paweł Maniecki b6dc274e6b docs: fix typo in requirements for NG8106 (#57772)
"Configuration requirements" referred to the wrong diagnostic (`invalidBananaInBox`)
likely due to a copy-paste error

PR Close #57772
2024-09-16 11:21:28 +02:00
Suryaprakash d5a27b8818 docs: Update guide for version 18 - unclear (possibly incorrect?) instruction for opting out of caching HTTP requests that require authentication (#57799)
PR Close #57799
2024-09-13 13:28:21 +02:00
Kristiyan Kostadinov 5c866942a1 fix(migrations): account for explicit standalone: false in migration (#57803)
Fixes that the standalone migration was duplicating the `standalone` flag if the declaration was `standalone: false`.

PR Close #57803
2024-09-13 10:48:42 +02:00
Pascal Weyrich 156f0d0c1b docs: fix example for defer on hover on an alternative element (#57788)
PR Close #57788
2024-09-12 11:23:34 +02:00
Doug Parker fab6462517 docs: add section on removing Zone.js imports/dependency from the build (#57784)
PR Close #57784
2024-09-12 10:54:10 +02:00
Lang 9bf60b06c9 docs: use correct example (#57783)
PR Close #57783
2024-09-11 21:25:26 +00:00
Simon Knott 5846d134e6 docs: add Playwright to E2E tools (#57734)
docs: alphabetisize list

PR Close #57734
2024-09-11 21:24:53 +00:00
Jessica Janiuk 7339e08c8a release: cut the v18.2.4 release 2024-09-11 15:59:31 -04:00
danielbulic 98ffbb3d05 docs: fix link of DEVELOPER.md in CONTRIBUTING.md (#57775)
PR Close #57775
2024-09-11 18:55:21 +00:00
Santosh Yadav fa4107e413 docs: add examples for localize (#57534)
PR Close #57534
2024-09-10 20:33:19 +00:00
Jan Olaf Martin c4b19c4d43 docs: fix link from zoneless to OnPush (#57760)
PR Close #57760
2024-09-10 20:28:00 +00:00
Angular Robot a919c6cae3 build: update all non-major dependencies to ~5.3.0 (#57754)
See associated pull request for more information.

PR Close #57754
2024-09-10 16:31:46 +00:00
Arnau Gómez Farell d0fe538c8a docs(core): fix typo (#57741)
Fix typo where two variable names should be the same but are different.
PR Close #57741
2024-09-09 18:23:06 +00:00
Rachid O 8d278390b2 docs: fix broken link in roadmap page (#57722)
PR Close #57722
2024-09-09 14:40:29 +00:00
Rachid O 9d730ed0b4 docs: fix typo in steps/12-enable-routing/README.md (#57722)
PR Close #57722
2024-09-09 14:40:29 +00:00
Kristiyan Kostadinov b619d6987e fix(compiler): produce less noisy errors when parsing control flow (#57711)
Currently several parsing errors in the new control flow (e.g. missing `track` expression) produce errors whose span targets the entire block. This can be really noisy in the IDE where the error can span many lines in the template.

These changes switch to highlighting just the start of the block.

PR Close #57711
2024-09-09 14:10:02 +00:00
Shannon V.C. 0a86b49ab0 docs: add missing link in templates guide overview (#57718)
This addresses a minor issue where the "See whitespace in templates" phrase was not linked to its corresponding documentation section

PR Close #57718
2024-09-09 14:09:07 +00:00
diamondhero1 4cb07e6a56 docs: extraneous backslashes removed (#57725)
PR Close #57725
2024-09-09 13:58:50 +00:00
Michael Small 981255c77e docs(docs-infra): remove more unused standalone imports (#57703)
PR Close #57703
2024-09-09 13:58:21 +00:00
Matthieu Riegler 0843f31474 docs: Enable direct links to class/interface methods for @link (#57615)
PR Close #57615
2024-09-06 16:03:37 +00:00
Seyed Alireza Mirehe 98ad3afa57 docs: fix typo in view queries documentation (#57228)
The typo in the [queries document](https://angular.dev/guide/components/queries#content-queries) has been corrected:
Queries never **pierce** through component boundaries.

PR Close #57228
2024-09-06 16:03:03 +00:00
Matthieu Riegler e4bed4b0db docs(docs-infra): Remove focus on navigation (#57695)
PR Close #57695
2024-09-06 16:02:13 +00:00
Matthieu Riegler bb8e7cea28 docs(docs-infra): fix extraction tests (#57694)
PR Close #57694
2024-09-06 15:07:48 +00:00
Kristiyan Kostadinov 9895e4492f fix(migrations): replace leftover modules with their exports during pruning (#57684)
Currently during the module pruning stage of the standalone migration we assume that any leftover modules which only have `imports` and `exports` can safely be removed. That can be incorrect for the cases where some parts of the app were converted to standalone outside of the migration.

These changes update the logic so that such modules are replaced with the `exports` which are used within the specific component.

Fixes #51420.

PR Close #57684
2024-09-06 14:15:50 +00:00
Matthieu Riegler 09f2ec0a12 refactor(compiler-cli): Do extract symbols from private modules. (#57611)
Modules like `core/primitives` are considered private and their symbols shouldn't be exposed nor linked in the docs.

PR Close #57611
2024-09-06 14:15:19 +00:00
Ben Hong 1fa59425db docs: add redirect for pipes guide (#57681)
PR Close #57681
2024-09-06 14:14:08 +00:00
Matthieu Riegler 82bd547d23 docs(docs-infra): remove unused standalone imports (#57680)
PR Close #57680
2024-09-06 14:13:36 +00:00
Ben Hong af4e39a0ab docs: update template in-depth guide (#57676)
PR Close #57676
2024-09-05 17:57:36 +00:00
Sheik Althaf 3bce676996 refactor(devtools): use signal apis in visualizer and devtools-tabs (#57192)
Refactor the visualizer and devtools-tabs components to use signal apis, in future we can make the components onPush and zoneless

PR Close #57192
2024-09-05 17:23:34 +00:00
Matthieu Riegler 21f67b95b5 docs: display property details (#57622)
In case there are no signatures (like in an bare interface method), we display the implementation information.

PR Close #57622
2024-09-05 17:22:32 +00:00
Jan Olaf Martin 74d3fbf14b docs: clarify behavior of ng serve with server.ts (#57658)
See: https://stackoverflow.com/questions/77468756/how-to-use-express-server-in-dev-mode-in-angular-17-application-builder
See: https://github.com/angular/angular/issues/52929#issuecomment-1818078952

PR Close #57658
2024-09-05 14:06:32 +00:00
Matthieu Riegler 5014bb5b1c docs(docs-infra): enable tests for api-gen (#57618)
PR Close #57618
2024-09-05 14:05:05 +00:00
Sheik Althaf 004110c48d refactor(devtools): use signal apis for directive forest (#56998)
Refactor the directive-forest components to use signal apis, in future we can make the components onPush and zoneless

PR Close #56998
2024-09-04 21:00:12 +00:00
twerske 69ca33f4a4 docs: update top level banner styles (#57503)
PR Close #57503
2024-09-04 17:05:26 +00:00
Angular Robot 102ed0c131 build: update io_bazel_rules_sass digest to 5a7e3f4 (#57649)
See associated pull request for more information.

PR Close #57649
2024-09-04 17:04:54 +00:00
Andrew Kushnir f75429d8a8 release: cut the v18.2.3 release 2024-09-04 08:25:39 -07:00
Doug Parker c91c37728f release: bump Angular DevTools version to 1.0.18 (#57585)
PR Close #57585
2024-09-03 10:43:22 -07:00
Matthieu Riegler de68e049e4 fix(http): Dynamicaly call the global fetch implementation (#57531)
Instead of using the reference that existing when `FetchBackend` is setup.

fixes #57527

PR Close #57531
2024-09-03 09:10:23 -07:00
Ilia Brahinets 2324d9b15a docs: Fix 'EnvironmentInjector' description (#57582)
PR Close #57582
2024-09-03 08:50:05 -07:00
Angular Robot b494e9b5f7 build: update babel dependencies to v7.25.6 (#57634)
See associated pull request for more information.

PR Close #57634
2024-09-03 07:52:13 -07:00
Angular Robot 5d7e446ce2 build: update scorecard action dependencies (#57636)
See associated pull request for more information.

PR Close #57636
2024-09-03 07:51:22 -07:00
Alfredo González 00c0876626 docs(upgrade): change example wording on Input tutorial (#57625)
PR Close #57625
2024-09-03 07:48:16 -07:00
Matthieu Riegler 9c3aeb99bb docs(docs-infra): add support for extends/implements on API entries (#57588)
PR Close #57588
2024-09-03 07:47:25 -07:00
Matthieu Riegler 582aac49a7 refactor(compiler-cli): Add support for inheritance in API extraction (#57588)
This commit adds the `extends` and `implements` properties to the `ClassEntry` & `InterfaceEntry`

PR Close #57588
2024-09-03 07:47:25 -07:00
Matthieu Riegler 5256016695 docs(docs-infra): fix navigation between API pages (#57492)
PR Close #57492
2024-08-30 11:12:27 -07:00
Matthieu Riegler d2d72e369c docs(docs-infra): Add set of unit tests to the API markdown parsing (#57492)
PR Close #57492
2024-08-30 11:12:26 -07:00
Matthieu Riegler 641808fa72 docs(docs-infra): Improve styling around API pages (#57492)
PR Close #57492
2024-08-30 11:12:26 -07:00
Matthieu Riegler f859d5c156 docs(docs-infra): Improve support for deprecated methods (#57492)
PR Close #57492
2024-08-30 11:12:26 -07:00
Matthieu Riegler cbb36fc1ab docs: ensure the all _ are replaced by slash for API entry urls. (#57600)
fixes #57599

PR Close #57600
2024-08-30 07:39:37 -07:00
Andrew Kushnir 4d8da40574 Revert "refactor(compiler-cli): extract function overload separatly (#56489)" (#57594)
This reverts commit 37b88a5a98.

PR Close #57594
2024-08-29 16:21:47 -07:00
David Valentine 614c311a01 docs: update common-router-tasks to include sample dynamic fragment (#57590)
Remove specific reference to "Tour of Heroes" tutorial and add sample route for use in the example for reference.

Co-authored-by: Andrew Scott <atscott01@gmail.com>

PR Close #57590
2024-08-29 15:20:06 -07:00
Matthieu Riegler ed91b03e0f docs: api sub-entries should have / in urls. (#57593)
`@angular/animations/browser` => `api/animations/browser/xyz`

PR Close #57593
2024-08-29 15:19:00 -07:00
Matthieu Riegler 5007970c3d docs: revert changes on API entries urls (#57589)
The name `angular_` shouldn't be included in the URL.

fixes #57587

PR Close #57589
2024-08-29 14:25:27 -07:00
Matthieu Riegler c068045d69 docs(docs-infra): Add support for cross-links on API pages (#57346)
PR Close #57346
2024-08-29 13:39:59 -07:00
Matthieu Riegler 9814767d34 refactor(compiler-cli): Add a map of every symbols used inside a package (#57346)
This commit changes the structure of the API extraction files to include all symbols used inside a package.

The structure is a `Map`, Symbol => package
eg: 'ApplicationRef' => '@angular/core'

PR Close #57346
2024-08-29 13:39:59 -07:00
Matthieu Riegler 93bdbbc812 docs(docs-infra): Add dev-mode only mention for core/global (#57365)
PR Close #57365
2024-08-29 10:17:35 -07:00
Joey Perrott 07607716d4 ci: use version 13.15.1 of firebase-tools for deployment of doc sites (#57583)
Use 13.15.1 instead of tracking to latest to prevent unexpected and unbisectable changes.

PR Close #57583
2024-08-29 10:14:43 -07:00
Paul Gschwendtner d9c1004a35 refactor(migrations): ensure control flow analysis of signal input migration works with all TS versions (#57566)
Ensures that the control flow analysis version of the signal input works
with all TS versions currently supported.

PR Close #57566
2024-08-29 07:51:39 -07:00
Paul Gschwendtner bb29c8bbd7 refactor(migrations): cleanup TODOs in signal input migration (#57566)
Cleans up remaining TODOs in the signal input migration, and other
small clean ups.

PR Close #57566
2024-08-29 07:51:38 -07:00
Matthieu Riegler 8e1d6c71fc refactor(compiler-cli): extract function overload separatly (#56489)
in order for the docs to process function entry, this commit refactor function extraction by keeping the implementation as a the default entry and adds all the overloads into a separate array of entries.

PR Close #56489
2024-08-29 07:49:38 -07:00
Romain 98c6c05c6f docs(docs-infra): fix small mistake in inject-function.md (#57517)
PR Close #57517
2024-08-29 07:44:29 -07:00
Matthieu Riegler 3b7162d03c docs: Add mention to ENVIRONMENT_INITIALIZER (#57464)
Explicitly mention that `ENVIRONMENT_INITIALIZER` should not be async.

PR Close #57464
2024-08-29 07:43:48 -07:00
Vlad Boisa 3bc28678fa docs(docs-infra): remove hash before link (#57351)
Remove hash in link for correct view

Fixes #57349

docs(docs-infra): change the link

Change the link with name of class and method

docs: fix the link name

PR Close #57351
2024-08-29 07:42:13 -07:00
Matthieu Riegler 97fd311b6a docs(docs-infra): show usageNotes only for methods. (#57343)
Prior to this change, function and methods showed their usage notes which resulted in duplicate displays for functions.

Fixes #57339

PR Close #57343
2024-08-29 07:39:51 -07:00
Alex Rickabaugh 1853bbb061 release: cut the v18.2.2 release 2024-08-28 14:32:26 -07:00
Alex Rickabaugh 3ecf620cc5 Revert "fix(http): Dynamicaly call the global fetch implementation (#57531)" (#57571)
This reverts commit 21445a2932.

Reason: failing test

PR Close #57571
2024-08-28 12:42:27 -07:00
Paul Gschwendtner 36aa3af77b refactor(migrations): forward-fix conflicts with generic of ProgramInfo. (#57565)
One PR removed the generic of `ProgramInfo`, while another PR,
separately merged, introduced new instanes of `ProgramInfo`.

PR Close #57565
2024-08-28 09:17:34 -07:00
AleksanderBodurri adf32746c2 fix(devtools): correctly set environment injector path in the case where there are no element injectors (#57442)
Previously, when "Hide injectors with no providers" was toggled, it is possible for the injector tree visualizer to have no Element injectors to visualize. This caused a bug in the slicing logic that splits apart the environment and element injectors from DI resolution paths within the injector tree component in Angular DevTools.

Now, this logic is correctly handled when there are no element injectors to visualize.

PR Close #57442
2024-08-28 08:49:34 -07:00
Matthieu Riegler 5501e9c8ed docs: remove @searchKeywords (#57560)
This tag is not supported.

fixes #57540

PR Close #57560
2024-08-28 08:49:05 -07:00
Paul Gschwendtner 389933f079 refactor(migrations): allow tsurge migrations to run with just NgCompiler (#57562)
This is important so that migrations can easily be wired up in the
language service where only `NgCompiler` is available.

PR Close #57562
2024-08-28 08:47:46 -07:00
Jeremy Elbourn 674aed5984 docs: cover time-based statements in docs authoring guide (#57536)
Adds some additional guidance on writing content that is tied to a
specific version or point in time.

PR Close #57536
2024-08-28 08:45:10 -07:00
Paul Gschwendtner b14c864170 refactor(migrations): properly handle multi query migration (#57556)
Properly handles queries with multiple results, by extracting the
type from the `QueryList`.

Also adds more tests and handles imports.

PR Close #57556
2024-08-28 08:43:39 -07:00
Paul Gschwendtner 10fce207e0 refactor(migrations): initial migration logic for converting to signal queries (#57556)
Adds initial migration logic to convert decorator query declarations to
signal queries.

We will re-use more part of the signal input migration in follow-ups, to
properly migrate, and e.g. even handle control flow

PR Close #57556
2024-08-28 08:43:38 -07:00
Alex Rickabaugh eba3a0ade0 Revert "refactor(migrations): initial migration logic for converting to signal queries (#57525)" (#57555)
This reverts commit 6f5b435a69.

Reason: breaks g3

PR Close #57555
2024-08-27 14:03:06 -07:00
Alex Rickabaugh 3d48a72193 Revert "refactor(migrations): properly handle multi query migration (#57525)" (#57555)
This reverts commit f454ad3bcf.

Reason: breaks g3

PR Close #57555
2024-08-27 14:03:06 -07:00
Matthieu Riegler 5d2e243c76 fix(http): Dynamicaly call the global fetch implementation (#57531)
Instead of using the reference that existing when `FetchBackend` is setup.

fixes #57527

PR Close #57531
2024-08-27 13:34:19 -07:00
Andrew Scott 5e9661d69c fix(docs-infra): Fix scrolling in application (#57554)
Since the change to the after render hooks, the "this" context must be
correct for the destroy to work.

fixes #57552

PR Close #57554
2024-08-27 13:32:14 -07:00
Kristiyan Kostadinov df42d2be16 test: avoid leaking some LViews in tests (#57546)
We had some tests that were leaking LViews, because we were testing things like `createComponent`, but not destroying them afterwards. These changes clean up most of them, although there are a handful still left that I didn't have time to fully track down.

PR Close #57546
2024-08-27 13:29:09 -07:00
Kristiyan Kostadinov 106917af87 fix(core): avoid leaking memory if component throws during creation (#57546)
When we create the LView for a component, we track it in the `TRACKED_LVIEWS` map. It gets untracked when it is destroy, but if it throws during creation, the user won't have access to a `ComponentRef` in order to clean it up.

These changes automatically untrack the related LViews if the component couldn't be created.

PR Close #57546
2024-08-27 13:29:09 -07:00
Alan Agius b74b5f0c06 build: remove Sourcemaps from Schematics bundles (#57545)
This commit reduces the bundle size of `@angular/core` by 19.5 MB by excluding sourcemaps from the schematics.

Since the `esbuild` rule doesn't allow disabling sourcemaps directly, we work around this by setting the sourcemaps to `external`. Afterward, we filter the output to include only the `.js` files.

PR Close #57545
2024-08-27 13:27:33 -07:00
Angular Robot 4231404065 build: update github/codeql-action action to v3.26.5 (#57543)
See associated pull request for more information.

PR Close #57543
2024-08-27 13:25:58 -07:00
Angular Robot adcdfeca16 build: update dependency @babel/generator to v7.25.5 (#57542)
See associated pull request for more information.

PR Close #57542
2024-08-27 13:24:59 -07:00
Andrew Kushnir 4f8c406664 refactor(compiler): extend directive mock to avoid failing at matching logic (#57537)
This commit updates a directive mock instance to include an extra field that a compiler code was expecting, which caused issues while processing elements with local refs and exported directives.

PR Close #57537
2024-08-27 13:23:30 -07:00
Paul Gschwendtner 206b7be7f7 ci: fix fw-testing group triggering for schematics testing changes (#57526)
The `fw-testing` group should not trigger for schematic changes.

PR Close #57526
2024-08-27 13:21:48 -07:00
Andrew Scott 804925b114 fix(router): Do not unnecessarily run matcher twice on route matching (#57530)
This commit makes a small update to the route matching algorithm to
avoid running the matcher function of a route twice.

fixes #57511

PR Close #57530
2024-08-27 13:20:58 -07:00
Paul Gschwendtner 6a953c6ed4 refactor(migrations): properly handle multi query migration (#57525)
Properly handles queries with multiple results, by extracting the
type from the `QueryList`.

Also adds more tests and handles imports.

PR Close #57525
2024-08-27 13:20:19 -07:00
Paul Gschwendtner 01cae95596 refactor(migrations): initial migration logic for converting to signal queries (#57525)
Adds initial migration logic to convert decorator query declarations to
signal queries.

We will re-use more part of the signal input migration in follow-ups, to
properly migrate, and e.g. even handle control flow

PR Close #57525
2024-08-27 13:20:19 -07:00
AleksanderBodurri 371ad098d5 fix(devtools): ignore DOM Nodes from other frames when performing render tree detection (#57518)
Previously, if an application had DOM Nodes injected into it from other frames, DevTools would fail to parse component trees with the render tree strategy properly because of an instanceof Node check that the framework performs.

Now we check for instanceof Node before even calling framework debug APIs on DOM nodes so that we can skip nodes that come from other frames entirely.

PR Close #57518
2024-08-27 13:19:35 -07:00
Angular Robot 73ad6ea647 build: update dependency jsdom to v25 (#57514)
See associated pull request for more information.

PR Close #57514
2024-08-27 13:17:39 -07:00
Matthieu Riegler d3366815ee refactor(animations): Add loading strategy for the Async Animations (#57493)
In some cases apps need to schedule a bit later the loading of the animation module. This private token will allow to investigate which other strategy could be useful.

PR Close #57493
2024-08-27 13:15:51 -07:00
Doug Parker 76ec60d73c refactor(compiler): ensure context is always provided for WhitespaceVisitor (#56507)
When disabling `i18nPreserveSignificantWhitespaceForLegacyExtraction` I was looking at a test case with ICU messages containing leading and trailing whitespace:

```angular
<div i18n>
  {apples, plural, =other {I have many apples.}}
</div>
```

This would historically generate two messages:

```javascript
const MSG_TMP = goog.getMsg('{apples, plural, =other {I have many apples.}}');
const MSG_FOO = goog.getMsg(' {$ICU} ', { 'ICU': MSG_TMP });
```

But I found that I was getting just one message:

```javascript
const MSG_TMP = goog.getMsg(' {apples, plural, =other {I have many apples.}} ');
```

This is arguably an improvement, but changed the messages and message IDs, which isn't desirable with this option. I eventually traced this back to the `isIcu` initialization in [`i18n_parser.ts`](/packages/compiler/src/i18n/i18n_parser.ts):

```typescript
const context: I18nMessageVisitorContext = {
  isIcu: nodes.length == 1 && nodes[0] instanceof html.Expansion,
  // ...
};
```

[`_I18nVisitor.prototype.visitExpansion`](/packages/compiler/src/i18n/i18n_parser.ts) uses this to decide whether or not to generate a sub-message for a given ICU expansion:

```typescript
if (context.isIcu || context.icuDepth > 0) {
  // Returns an ICU node when:
  // - the message (vs a part of the message) is an ICU message, or
  // - the ICU message is nested.
  const expPh = context.placeholderRegistry.getUniquePlaceholder(`VAR_${icu.type}`);
  i18nIcu.expressionPlaceholder = expPh;
  context.placeholderToContent[expPh] = {
    text: icu.switchValue,
    sourceSpan: icu.switchValueSourceSpan,
  };
  return context.visitNodeFn(icu, i18nIcu);
}

// Else returns a placeholder
// ICU placeholders should not be replaced with their original content but with the their
// translations.
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
const phName = context.placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
context.placeholderToMessage[phName] = this.toI18nMessage([icu], '', '', '', undefined);
const node = new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
return context.visitNodeFn(icu, node);
```

Note that `isIcu` is the key condition between these two cases and depends on whether or not the ICU expansion has any siblings. The introduction of `WhitespaceVisitor` to `I18nMetaVisitor` trims insignificant whitespace, including empty text nodes not adjacent to an ICU expansion (from [`WhitespaceVisitor.prototype.visitText`](/packages/compiler/src/ml_parser/html_whitespaces.ts)):

```typescript
const isNotBlank = text.value.match(NO_WS_REGEXP);
const hasExpansionSibling =
  context && (context.prev instanceof html.Expansion || context.next instanceof html.Expansion);

if (isNotBlank || hasExpansionSibling) {
  // Transform node by trimming it...
  return trimmedNode;
}

return null; // Drop node which is empty and has no ICU expansion sibling.
```

`hasExpansionSibling` was intended to retain empty text nodes leading or trailing an ICU expansion, however `context` was `undefined`, so this check failed and the leading / trailing text nodes were dropped. This resulted in trimming the ICU text by dropping the leading / trailing whitespace nodes. Having only a single ICU expansion with no leading / trailing text nodes caused `_I18nVisitor` to initialize `isIcu` incorrectly and caused it to generate one message instead of two.

`WhitespaceVisitor` is supposed to get this context from `visitAllWithSiblings`. So the fix here is to make sure `WhitespaceVisitor` is always visited via this function which provides the required context. I updated all usage sites to make sure this context is use consistently and implemented the `WhitespaceVisitor.prototype.visit` method to throw when the context is missing to make sure we don't encounter a similar mistake in the future.

Unfortunately this broke one compliance test. Specifically the [`icu_logic/icu_only.js`](/home/douglasparker/Source/ng/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/icu_only.js) test which changed from generating:

```javascript
function MyComponent_Template(rf, ctx) {
  if (rf & 1) {
    i0.ɵɵi18n(0, 0);
  }
  // ...
}
```

To now generating:

```javascript
function MyComponent_Template(rf, ctx) {
  if (rf & 1) {
    i0.ɵɵtext(0, " ");
    i0.ɵɵi18n(1, 0);
    i0.ɵɵtext(2, "\n");
  }
  // ...
}
```

This test uses the default value `preserveWhitespaces: false` (`i18nPreserveSignificantWhitespaceForLegacyExtraction` should not affect compiled JS output, we already retain significant whitespace there). So what this indicates to me is that ICU logic is already broken because it's not preserving significant whitespace in this case. My change is probably a bug fix, but one which would affect the compiled runtime, which is not in scope here. The root cause is because using `visitAllWithSiblings` everywhere means the context is retained correctly in this case and the whitespace is leading/trailing an ICU message, therefore it is retained per the logic of `WhitespaceVisitor.prototype.visitText` I mentioned eariler.

To address this, I left one usage of `WhitespaceVisitor` using `html.visitAll` instead of `visitAllWithSiblings` to retain this bug. I has to lossen the assertion I put in `WhitespaceVisitor.prototype.visit` to make this possible, but it should still throw by default when misused, which is the important part.

PR Close #56507
2024-08-27 13:13:57 -07:00
Doug Parker 7bfe81700b refactor(compiler): add i18nPreserveWhitespaceForLegacyExtraction (#56507)
This configures whether or not to preserve whitespace content when extracting messages from Angular templates in the legacy (View Engine) extraction pipeline.

This includes several bug fixes which unfortunately cannot be landed without changing message IDs in a breaking fashion and are necessary to properly trim whitespace. Instead these bug fixes are included only when the new flag is disabled.

PR Close #56507
2024-08-27 13:13:57 -07:00
Michael van der Luit 3067633bc7 docs(docs-infra): Add query as an url query param to the api reference (#57062)
add an url query param to perform a search on api references directly via the url

PR Close #57062
2024-08-27 12:58:00 -07:00
AleksanderBodurri be82f282a4 fix(devtools): catch invalidated extension error to prevent devtools from spamming console (#55697)
When a browser extension is updated it becomes invalidated on currently open pages. If that extension then tries to send a message to those pages through `chrome.runtime.sendMessage(..)` then an error is thrown in the console

For Angular DevTools, this results in spamming the console with "Uncaught Error: Extension context invalidated." errors.

This commit catches that error and removes the event listener that triggers the `chrome.runtime.sendMessage(...)` call.

PR Close #55697
2024-08-27 12:54:44 -07:00
realstealthninja 03b35270b0 docs: fix typo in recommendations.ts (#57521)
PR Close #57521
2024-08-26 09:17:44 -07:00
Matthieu Riegler fe9005ce8e docs: fix formating in class-binding (#57447)
fixes #57430

PR Close #57447
2024-08-26 09:12:55 -07:00
Bjarki 03ec620e31 fix(upgrade): Address Trusted Types violations in @angular/upgrade (#57454)
Angular applications that are AngularJS hybrids are currently unable to
adopt Trusted Types due to violations eminating from an innerHTML
assignment in the @angular/upgrade package. This commit allows
developers of such applications to optionally ignore this class of
violations by configuring the Trusted Types header to allow the new
angular#unsafe-upgrade policy.

Note that the policy is explicitly labeled as unsafe as it does not in
any way mitigate the security risk of using AngularJS in an Angular
application, but does unblock Trusted Types adoption enabling XSS
protection for other parts of the application.

The implementation follows the approach taken in @angular/core;
see packages/core/src/util/security.

PR Close #57454
2024-08-26 09:04:48 -07:00
Thomas Nguyen 6d3a2af146 fix(core): Do not bubble capture events. (#57476)
These should only fire if the target is the same as the targetElement. Also, delete an out of date test since capture/non-capture tests are separately covered.

PR Close #57476
2024-08-23 14:46:55 -07:00
Paul Gschwendtner 223b7857ef refactor(migrations): replacements in tsurge are always serializable (#57501)
This removes an unnecessary type wrapping that can be removed because
`Replacements` is expected to be serializable by default.

Similar to how it's done in `TsurgeComplexMigration`.

PR Close #57501
2024-08-23 13:49:31 -07:00
Angular Robot 5bdae0387f build: update dependency mermaid to v11 (#57499)
See associated pull request for more information.

PR Close #57499
2024-08-23 13:00:59 -07:00
Peter 034b32c133 docs: fix typo of ChangeDetectionStrategy in the zoneless guide (#57482)
PR Close #57482
2024-08-23 12:59:09 -07:00
Kristiyan Kostadinov 2a89e184e0 fix(zone.js): more robust check for promise-like objects (#57388)
Fixes that Zone.js wasn't checking properly if an object is promise-like.

Fixes #57385.

PR Close #57388
2024-08-23 12:58:29 -07:00
Santosh 9b6865ec03 docs: add test for i18n code block and fix rendering (#57210)
PR Close #57210
2024-08-23 12:57:15 -07:00
Alex Rickabaugh 6059ca8f1f refactor(core): restructure AfterRenderManager to connect related phases (#57453) (#57504)
The `afterRender` infrastructure was first implemented around the idea of
independent, singular hooks. It was later updated to support a spec of
multiple hooks that pass values from one to another as they execute, but the
implementation still worked in terms of singular hooks under the hood. This
creates a number of maintenance issues, and a few bugs. For example, when
one hook fails, further hooks in the pipeline should no longer execute, but
this was hard to ensure under the old design.

This refactoring restructures `afterRender` infrastructure significantly to
introduce the concept of a "sequence", a collection of hooks of different
phases that execute together. Overall, the implementation is simplified
while making it more resilient to issues and future use cases, such as the
upcoming `afterRenderEffect`.

As part of this refactoring, the `internalAfterNextRender` concept is
removed, as well as the unused `queueStateUpdate` concept which used it.

PR Close #57453

PR Close #57504
2024-08-23 12:48:01 -07:00
Alex Rickabaugh 800f6c8ca3 refactor(core): track dirtiness bits in ApplicationRef (#57453) (#57504)
Previously the zoneless scheduler had a concept of whether views needed to
be refreshed or not, based on the notification type that was received. It
tracked this information as a boolean.

This commit refactors things to track dirtiness in `ApplicationRef` itself,
as a `dirtyFlags` field with bits corresponding to either view tree
dirtiness or after-render hooks.

PR Close #57453

PR Close #57504
2024-08-23 12:48:01 -07:00
Paul Gschwendtner 87c594b90b refactor(migrations): add support for simpler variant of tsurge migration (#57484)
Introduces a simpler, smaller variant of the current `Tsurge` migration
class. The difference is simply that for the migration phase (the third
stage), some migrations do not need a full set of workers re-analyzing
every compilation unit again to compute the "final migration
replacements".

This can be the case, for example, if a migration eagerly computes all
replacements in the analyze stage, visiting every unit, and then after
deriving the global metadata, problematic replacements are simply
filtered (e.g. via some unique IDs again).

PR Close #57484
2024-08-23 12:01:59 +00:00
Paul Gschwendtner f133489a16 refactor(migrations): clean up unused google3 code from signal input migration (#57484)
The code was replaced by the automatic Tsurge batch runner. This commit
cleans up the now unused code.

PR Close #57484
2024-08-23 12:01:59 +00:00
Otmane 09deb24d74 docs: fix indentation of file tree in app-shell (#57436)
This commit adds indentations to the file tree section in `app-shell.md`, it makes the tree easier to read. The indentation is now two spaces instead of no spaces.

PR Close #57436
2024-08-22 09:11:50 -07:00
Paweł Kubiak f1bc6f895b docs(docs-infra): add top level banner component (#57458)
docs(docs-infra): add top level banner component

- create top level banner component
- write unit tests
- close banner and keep state in the local storage
- fix: support screens of tablets and phones

PR Close #57458
2024-08-22 09:06:47 -07:00
Matthieu Riegler 5f95afb331 docs: new date for v19 (#57461)
PR Close #57461
2024-08-22 09:04:41 -07:00
Andrew Kushnir b30bc4531a refactor(compiler): create an internal util method to detect matching directives and pipes (#57466)
This commit adds an internal util method that allows to detect:

* which selectors are matching nodes in a template
* which pipes are present in a template

Both directives and pipes are split into 2 buckets: eagerly used and the ones that might potentially be defer-loaded.

PR Close #57466
2024-08-22 09:04:04 -07:00
Dylan Hunn a6225a6cdd release: cut the v18.2.1 release 2024-08-22 09:00:21 -07:00
Matthieu Riegler 69b20d8391 docs(docs-infra): fix regressions around members & deprecations (#57465)
This commit fixes regressions introduced by #57255

PR Close #57465
2024-08-21 11:26:27 -07:00
steciuk 6089772fe8 docs: remove extra space in DI example code (#57472)
PR Close #57472
2024-08-21 11:25:54 -07:00
Angular Robot 1a5f5ee5ce build: update github/codeql-action action to v3.26.3 (#57455)
See associated pull request for more information.

PR Close #57455
2024-08-20 13:20:01 -07:00
Angular Robot 76808cc328 build: update dependency saucelabs to v8 (#57413)
See associated pull request for more information.

PR Close #57413
2024-08-20 13:19:11 -07:00
Adam Koch 980c64abaa docs: remove incorrect documentation around the use of timezone abbreviations (#57425)
The ability to use an abbreviated timezone was removed in v5 (issue https://github.com/angular/angular/issues/20225 discusses this) and no alternative has been merged. See issue https://github.com/angular/angular/issues/40865 and PR https://github.com/angular/angular/pull/48482 The text suggests there is a way to pass in a timezone other than by an offset but there isn't.

PR Close #57425
2024-08-20 10:51:37 -07:00
Paul Gschwendtner df3e9c1661 refactor(migrations): leverage tsurge for signal input migration (#57451)
This commit simplifies the batching support for the signal input
migration by using the new tsurge framework we've built.

This allows for consistent setup across all possible entry-points and
also simplifies the 1P setup given that we can simply use the Tsurge
macros, instead of having to maintain our own Go-based runner.

PR Close #57451
2024-08-19 22:44:43 -07:00
Paul Gschwendtner cb7d817edd refactor(migrations): improve generic assignability in tsurge and pass more info (#57451)
* Improves some of the generic assignability for tsurge. Anything is
  allowed to be returned from an overridden `prepareProgram` method.
  This is useful for the signal input migration.
* Passes the absolute root paths to migrations. This is helpful for the
  signal input migration and there is no other way to access it. It's
  better to pass specifically, compared to passing the whole unsafe
  `ParsedConfiguration` object.

PR Close #57451
2024-08-19 22:44:43 -07:00
Angular Robot 8645152f8c build: update dependency ngx-progressbar to v12 (#57452)
See associated pull request for more information.

Closes #57443 as a pr takeover

PR Close #57452
2024-08-19 22:43:49 -07:00
Matthieu Riegler 0979ca0354 docs(docs-infra): Fix heading parsing (#57386)
fixes #57384

PR Close #57386
2024-08-19 09:24:08 -07:00
Matthieu Riegler af20b2a161 docs(docs-infra): remove remaining ngZone (#57229)
PR Close #57229
2024-08-19 09:22:01 -07:00
Matthieu Riegler b5719ba5eb docs(docs-infra): Add tests for marked rendering (#57344)
On top of #57338, to make sure we prevent similar regressions on marked rendering.

PR Close #57344
2024-08-19 09:21:17 -07:00
Michael Derfler c4901d4dfe docs: fix links to docs (#57391)
PR Close #57391
2024-08-19 09:20:16 -07:00
Andrew Kushnir 1682c60938 docs: add eager to lazy route migration (#57421)
PR Close #57421
2024-08-19 09:19:43 -07:00
Andrew Kushnir c65ab136bf docs: cleanup existing migration docs for consistency (#57421)
PR Close #57421
2024-08-19 09:19:43 -07:00
Andrew Kushnir c89be37193 docs: drop references to removed typed-forms migration (#57421)
PR Close #57421
2024-08-19 09:19:43 -07:00
Andrew Kushnir f723288adf docs: drop references to removed module-with-providers migration (#57421)
PR Close #57421
2024-08-19 09:19:42 -07:00
Joey Perrott 58e00deb34 fix(docs-infra): leverage http_server rule from @angular/build-tooling for adev local serving (#57428)
Use the http_server rule to serve adev locally to allow for slightly faster rebuilds and livereload on changes

PR Close #57428
2024-08-19 09:18:21 -07:00
Paul Gschwendtner 57db366522 refactor(migrations): framework to build batchable migrations (#57396)
Introduces a migration framework to build batchable migrations that
can run in Large Scale mode against e.g. all of Google, using workers.

This is the original signal input migration infrastructure extracted
into a more generic framework that we can use for writing additional
ones for output, signal queries etc, while making sure those are not
scoped to a single `ts.Program` that limits them to per-directory
execution in very large projects (e.g. G3).

The migration will be updated to use this, and in 1P we will add
helpers to easily integrate such migrations into a Go-based pipeline
runner.

PR Close #57396
2024-08-17 08:54:16 -05:00
Andrew Kushnir 3e6ee7eaff docs: update CHANGELOG to drop a breaking change note from 18.2.0 version (#57420)
This commit updates the CHANGELOG to remove an entry about a breaking change in Zone.js, since the change was not actually released. It will be released separately in Zone.js package.

PR Close #57420
2024-08-16 11:20:46 -04:00
Andrew Kushnir 086b754cef docs: add withEventReplay and withI18nSupport links to provideClientHydration docs (#57382)
PR Close #57382
2024-08-15 16:10:28 -04:00
vladboisa 4bd9ba714c docs(docs-infra): move link tag for correct view (#57395)
Move the link tag to the down, for correctly parsing of '@link'

Fixes #57332

PR Close #57395
2024-08-15 15:51:53 -04:00
cexbrayat 3b63082384 fix(migrations): avoid migrating route component in tests (#57317)
The migration was migrating all files in a project (like most migrations).
As there is no gain in migrating components used in test files. Excluding the test files reduces the migration noise.

PR Close #57317
2024-08-15 15:51:22 -04:00
David LJ 25bc810f83 docs: update version table for 18.2 (#57414)
PR Close #57414
2024-08-15 15:50:54 -04:00
Andrew Scott 9de30a7b1c fix(core): Allow zoneless scheduler to run inside fakeAsync (#56932)
The zoneless scheduler callback was executed in the root zone rather
than simply in `runOutsideAngular` to allow us to land the hybrid mode
change detection (scheduler always enabled, even for zones) without
breaking a ton of existing `fakeAsync` tests that could/would fail with
the "timer(s) still in queue" error. However, this caused another
problem: when a test executes inside `fakeAsync`, it cannot flush the
scheduled time. A similar problem exists with event and run coalescing (#56767).
This change would allow `fakeAsync` to flush the zoneless-scheduled
change detections and minimize breaking existing tests
by flushing pending timers at the end of the test, which actually now
matches what's done internally.

PR Close #56932
2024-08-15 12:32:24 -04:00
Matthieu Riegler b1ed7e2b5f docs(docs-infra): Add support for function/method overloads (#57379)
PR Close #57379
2024-08-15 12:12:26 -04:00
Kristiyan Kostadinov 6b4357fae4 fix(migrations): preserve type when using inject decorator (#57389)
Updates the migration so that it passes the type as a generic in the case of `@Inject(SOME_TOKEN) foo: SomeType`. This is done for two reasons:
1. It's a fairly common pattern and it ensures that the code can still be compiled.
2. It avoids leaving behind unused imports.

PR Close #57389
2024-08-15 12:11:59 -04:00
Kristiyan Kostadinov 0bb649b8fa fix(migrations): account for members with doc strings and no modifiers (#57389)
Fixes that the migration was duplicating the doc strings of members that don't have modifiers.

PR Close #57389
2024-08-15 12:11:59 -04:00
Andrew Kushnir 286012fb89 fix(core): handle hydration of components that project content conditionally (#57383)
This commit fixes an issue when hydration serialization tries to calculate DOM path to a content projection node (`<ng-content>`), but such nodes do not have DOM representation.

Resolves #56750.

PR Close #57383
2024-08-15 11:22:05 -04:00
Erik Wegner 9155f75e36 docs(common): fix example code (#57406)
Fix example code for interceptor.

PR Close #57406
2024-08-15 10:46:03 -04:00
Jeremy Elbourn 93410eda0a docs: add small clarification to ng-content (#57408)
This expands the recommendation to avoid conditionally showing `<ng-content>` with additional context

PR Close #57408
2024-08-15 10:45:16 -04:00
Andrew Kushnir d49e083999 release: cut the v18.2.0 release 2024-08-14 09:40:36 -07:00
Matthieu Riegler e50d239e80 docs(docs-infra): fix markdown link rendering (#57377)
fixes #57376

PR Close #57377
2024-08-13 15:53:56 -07:00
Thomas Nguyen e30c60e89f refactor(core): Add experimental support to have one event contract when there are multiple apps on the page. (#57355)
This may be removed if this turns out not to work out so well...

PR Close #57355
2024-08-13 12:10:35 -07:00
Thomas Nguyen 8104fc2126 refactor(core): Call stopPropagation and preventDefault unconditionally within the patched methods. (#57354)
This fixes a few tests in g3 and is a bug fix for the event dispatcher. Otherwise, bubbling might
continue.

PR Close #57354
2024-08-13 12:09:57 -07:00
Thomas Nguyen 7accd9d885 fix(core): Account for addEventListener to be passed a Window or Document. (#57354)
This happened to work for other event listeners since both had a
addEventListener method.

PR Close #57354
2024-08-13 12:09:57 -07:00
Andrew Scott 334c99f968 refactor(docs-infra): Update search results to display content when it is matched (#57298)
This commit updates the search results to query for the content as well
as a snippet of the content for display when it's the content that
matches the query rather than any of the headers.

PR Close #57298
2024-08-13 12:07:49 -07:00
Kristiyan Kostadinov b1a9d0f4de fix(migrations): avoid duplicating comments when generating properties (#57367)
Updates the logic that generates new component properties to avoid duplicating their doc strings.

PR Close #57367
2024-08-13 09:54:25 -07:00
Kristiyan Kostadinov 5d76401ff5 fix(migrations): preserve optional parameters (#57367)
Makes it so the inject migration preserves the optional token when declaring a parameter. This came up in some testing as something that can be potentially breaking for classes that implement interfaces.

PR Close #57367
2024-08-13 09:54:25 -07:00
Andrew Kushnir de85979648 fix(core): skip hydration for i18n nodes that were not projected (#57356)
This commit fixes an issue that happens when an i18n block is defined as a projectable content, but a parent component doesn't project it. With an extra check added in this commit, the code will be taking a regular "creation" pass instead of attempting hydration.

Resolves #57301.

PR Close #57356
2024-08-13 09:42:42 -07:00
Paul Gschwendtner bbc970bb0b refactor(migrations): always add readonly to migrated signal inputs (#57368)
Signal inputs are no longer updated by assignment, unlike `@Input()`, so
a good practice is adding `readonly` for the `InputSignal`— which should
never be swapped out.

This is a safe operation because the migration skips all inputs that are
being written anyway.

PR Close #57368
2024-08-13 09:41:09 -07:00
Paul Gschwendtner 87d00d26ff refactor(migrations): use input() shorthand if possible in input migration (#57368)
In some cases, the migration can detect when `input()` as a shorthand
may be usable. This commit adds such detection and migrates inputs to
this form when possible.

PR Close #57368
2024-08-13 09:41:08 -07:00
Matthieu Riegler 84752069f2 docs(docs-infra): Update marked to 14 (#57363)
This is a backport of the marked update and the fix that went with it (#57338)

PR Close #57363
2024-08-13 09:33:04 -07:00
Angular Robot d3b46ade94 build: update scorecard action dependencies (#57358)
See associated pull request for more information.

PR Close #57358
2024-08-13 09:30:16 -07:00
Angular Robot 1828d11d84 build: update dependency @babel/core to v7.25.2 (#57191)
See associated pull request for more information.

PR Close #57191
2024-08-13 09:29:35 -07:00
Paul Gschwendtner b8c82fa2f7 refactor(migrations): handle jit: true component templates in signal input migration (#57347)
Components with `jit: true` are not processed by the Angular compiler,
so we cannot ask the template checker for the parsed template; simply
because the template wasn't attempted to be parsed.

We still can migrate simple cases of such components, commonly seen in
unit tests. We do this by manually parsing the template and making use
of the reference fallback resolution that is also used for host bindings
(where we don't have any type check block information).

PR Close #57347
2024-08-12 15:17:41 -07:00
Paul Gschwendtner fa77c9e5b4 refactor(migrations): expose angular compiler options to signal input migration (#57347)
Instead of exposing just the `ts.CompilerOptions`, we should expose the
actual Angular compiler options throuhgout the signal input migration.

This will be useful for parsing templates, in cases of JIT-opted
components.

PR Close #57347
2024-08-12 15:17:40 -07:00
Paul Gschwendtner 5d16c286bf refactor(migrations): handle safe property reads in signal input migration (#57318)
As of this commit, the migration will also inspect safe property reads
and migrate them, if they reference an input that is being migrated.

PR Close #57318
2024-08-12 12:12:23 -07:00
Paul Gschwendtner 604270619a perf(migrations): speed up signal input migration by combining two analyze phases (#57318)
Instead of revisiting each source file, and each of its child nodes
twice, we now visit them together using a grouped AST visitor that only
traverses each source file once.

This seemed to speed up migration by 6-8% locally, but is likely
noticable better with large compilation scopes.

PR Close #57318
2024-08-12 12:12:23 -07:00
Paul Gschwendtner 1b546975f0 refactor(migrations): use import manager in signal input migration (#57318)
Instead of fiddling manually with the imports, which worked well, but
comes at a cost of complexity— we are now using the canonical import
manager. This simplifies deletion, insertion and updating of imports.

Notably, our import manager is not super great at preserving whitespaces
right now, but we assume a formatter runs over migrated code anyway.

PR Close #57318
2024-08-12 12:12:23 -07:00
Paul Gschwendtner 16ae748257 refactor(migrations): add best effort mode to signal input migration (#57318)
Introduces a best effort mode for the signal input migration. This mode
can be used to aggresively migrate as much as possible, ignoring most
of the incompatibility reasons, like "writes to the input".

PR Close #57318
2024-08-12 12:12:22 -07:00
Paul Gschwendtner ecb0f8f161 refactor(migrations): add initial docs for signal input migration incompatibility reasons (#57318)
Adds a markdown document capturing some of the incompatibilty reasons
on why the input wasn't migrated.

PR Close #57318
2024-08-12 12:12:22 -07:00
Gilad Mautner cea3e4b594 docs: reorder afterView and afterContent related lifecycle hooks (#57307)
PR Close #57307
2024-08-09 13:55:39 -07:00
Paul Gschwendtner 448279f57e refactor(migrations): detect if an input is not narrowed and can be migrated (#57308) (#57323)
By default, we don't migrate inputs if they are part of e.g. `@if` for
now. That is because we don't have the template narrowing feature
available yet.

To improve impact of the migration until we have the narrowing, we add
some additional checks that allow us to migrate instances of inputs that
are part of e.g. `@if` but are actually not used inside (and hence are
guaranteed to be _not_ narrowed).

PR Close #57308

PR Close #57323
2024-08-09 12:17:34 -07:00
Andrew Kushnir 5558e275ee fix(core): take skip hydration flag into account while hydrating i18n blocks (#57299)
This commit updates serialization and hydration i18n logic to take into account situations when i18n blocks are located within "skip hydration" blocks.

Resolves #57105.

PR Close #57299
2024-08-09 08:07:49 -07:00
Andrew Kushnir 86216792fd fix(core): complete post-hydration cleanup in components that use ViewContainerRef (#57300)
Previously, if a component injects a `ViewContainerRef`, the post-hydration cleanup process doesn't visit inner views to cleanup dehydrated views in nested LContainers. This commit updates the logic to recognize this situation and enter host LView to complete cleanup.

Resolves #56989.

PR Close #57300
2024-08-09 08:07:13 -07:00
Kristiyan Kostadinov cab6c23602 refactor(migrations): add internal cleanup logic (#57315)
Expands the `inject` migration to add some cleanups that are only relevant internally. Externally this isn't exposed to users.

PR Close #57315
2024-08-09 08:02:34 -07:00
Andrew Scott 296216cbe1 fix(core): Allow hybrid CD scheduling to support multiple "Angular zones" (#57267)
This commit updates the inside/outside NgZone detection of the hybrid CD
scheduling to track the actual instance of the NgZone being used rather
than the name "Angular" (how `isInsideAngularZone` works). This allows
the scheduling to work correctly when there are multiple versions of
Angular running on the page.

fixes #57261

PR Close #57267
2024-08-08 10:46:26 -07:00
Thomas Nguyen 9af760eb51 fix(core): Account for addEventListener to be passed a Window or Document. (#57282)
This happened to work for other event listeners since both had a
addEventListener method.

PR Close #57282
2024-08-08 08:32:11 -07:00
Kristiyan Kostadinov b16dd6d67f fix(compiler-cli): generate valid TS 5.6 type checking code (#57303)
Currently in some scenarios the compiler generates code like `null as any ? foo : bar` which will be invalid with [an upcoming TypeScript change](https://devblogs.microsoft.com/typescript/announcing-typescript-5-6-beta/#disallowed-nullish-and-truthy-checks). These changes switch to generating `0 as any` which is exempt from the change.

**Note:** I'm not starting the work to fully get us on TS 5.6 until the 18.2 release comes out, but this change is necessary to unblock an internal team.

PR Close #57303
2024-08-08 08:30:13 -07:00
Jessica Janiuk 02d613ef9c release: cut the v18.2.0-rc.0 release 2024-08-07 12:27:07 -07:00
3927 changed files with 38121 additions and 19562 deletions
+2 -3
View File
@@ -158,9 +158,8 @@ 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
# --prerender_adev will run adev build/serve in a full mode, performing prerendering
build --flag_alias=prerender_adev=//adev:prerender_adev
####################################################
# User bazel configuration
+1 -1
View File
@@ -1,5 +1,5 @@
name: 'Docs or angular.dev Bug Report'
description: Report an issue in Angular's documentation or angular.io application
description: Report an issue in Angular's documentation or angular.dev application
body:
- type: textarea
+1 -1
View File
@@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: Security issue disclosure
url: https://angular.io/guide/security#report-issues
url: https://angular.dev/best-practices/security#report-issues
about: Report a security issue in Angular Framework, CDK, Material, or CLI
- name: Angular CLI
url: https://github.com/angular/angular-cli/issues/new/choose
+1 -1
View File
@@ -18,7 +18,7 @@ What kind of change does this PR introduce?
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] angular.io application / infrastructure changes
- [ ] angular.dev application / infrastructure changes
- [ ] Other... Please describe:
@@ -103,7 +103,7 @@ export async function setupRedirect(deployment: Deployment) {
}
function firebase(cmd: string, cwd?: string) {
spawnSync('npx', `-y firebase-tools@latest ${cmd}`.split(' '), {
spawnSync('npx', `-y firebase-tools@13.15.1 ${cmd}`.split(' '), {
cwd,
encoding: 'utf-8',
shell: true,
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
name: 'Saucelabs legacy test job'
description: 'Runs tests against Saucelabs (outside of Bazel)'
runs:
using: 'composite'
steps:
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Starting Saucelabs tunnel service
shell: bash
run: ./tools/saucelabs/sauce-service.sh run &
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
# test in all supported browsers on Saucelabs anyway until this job can be removed.
- name: Preparing Bazel-generated fixtures required in legacy tests
shell: bash
run: |
yarn bazel build //packages/core/test:downleveled_es5_fixture //packages/common/locales
# Needed for the ES5 downlevel reflector test in `packages/core/test/reflection`.
mkdir -p dist/legacy-test-out/core/test/reflection/
cp dist/bin/packages/core/test/reflection/es5_downleveled_inheritance_fixture.js \
dist/legacy-test-out/core/test/reflection/es5_downleveled_inheritance_fixture.js
# Locale files are needed for i18n tests running within Saucelabs. These are added
# directly as sources so that the TypeScript compilation of `/packages/tsconfig.json`
# can succeed. Note that the base locale and currencies files are checked-in, so
# we do not need to re-generate those through Bazel.
mkdir -p packages/common/locales/extra
cp dist/bin/packages/common/locales/*.ts packages/common/locales
cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra
- name: Build bundle of tests to run on Saucelabs
shell: bash
run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs
- name: Wait and confirm Saucelabs tunnel has connected
shell: bash
run: ./tools/saucelabs/sauce-service.sh ready-wait
- name: Running tests on Saucelabs.
shell: bash
run: KARMA_WEB_TEST_MODE=SL_REQUIRED yarn karma start ./karma-js.conf.js --single-run
- name: Stop Saucelabs tunnel service
shell: bash
run: ./tools/saucelabs/sauce-service.sh stop
+1 -1
View File
@@ -4,7 +4,7 @@ description: 'Installs the dependencies using Yarn'
runs:
using: 'composite'
steps:
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4
- uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4
with:
path: |
./node_modules/
+5 -5
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@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel build //adev:build --config=release
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@03b8a7dffd1205e061f0bee949024ebefc2a6592
run: yarn bazel build //adev:build --prerender_adev --config=release
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
workflow-artifact-name: 'adev-preview'
pull-number: '${{github.event.pull_request.number}}'
+2 -2
View File
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
with:
token: '${{secrets.GITHUB_TOKEN}}'
@@ -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@03b8a7dffd1205e061f0bee949024ebefc2a6592
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'adev-preview'
@@ -16,6 +16,6 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@03b8a7dffd1205e061f0bee949024ebefc2a6592
- uses: angular/dev-infra/github-actions/branch-manager@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
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@03b8a7dffd1205e061f0bee949024ebefc2a6592
- uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
bazelrc: ./.bazelrc.user
+30 -41
View File
@@ -1,4 +1,4 @@
name: CI
name: CI (push)
on:
push:
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Install node modules
@@ -41,13 +41,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Run unit tests
@@ -59,13 +59,13 @@ jobs:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile --network-timeout 100000
- name: Run CI tests for framework
@@ -76,30 +76,32 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev in fast mode to ensure it continues to work
run: yarn bazel build //adev:build --fast_adev --config=release
- name: Run tests
run: yarn bazel test //adev:test
run: yarn bazel build //adev:build --config=release
# TODO: re-enable tests once the next release is shipped
# Tests are broken because of https://github.com/angular/angular/issues/54858
# - name: Run tests
# run: yarn bazel test //adev/...
publish-snapshots:
runs-on:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -111,7 +113,7 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
node-module-directories: |
@@ -119,9 +121,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@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- run: |
@@ -152,31 +154,18 @@ jobs:
- run: yarn --cwd packages/zone.js/test/typings install --frozen-lockfile --non-interactive
- run: yarn --cwd packages/zone.js/test/typings test
bazel-saucelabs:
saucelabs:
runs-on: ubuntu-latest-4core
env:
JOBS: 2
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@03b8a7dffd1205e061f0bee949024ebefc2a6592
- name: Set up Sauce Tunnel Daemon
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
- name: Run all saucelabs bazel tests
run: |
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)')
yarn bazel test --config=saucelabs --jobs=$JOBS ${TESTS}
- uses: ./.github/actions/saucelabs-legacy
adev-deploy:
needs: [adev]
@@ -184,15 +173,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel build //adev:build --config=release
run: yarn bazel build //adev:build --prerender_adev --config=release
- name: Deploy to firebase
uses: ./.github/actions/deploy-docs-site
with:
+2 -2
View File
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: angular/dev-infra/github-actions/commit-message-based-labels@11bd341a82719997a59039363f716bea76b88459
- uses: angular/dev-infra/github-actions/commit-message-based-labels@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: angular/dev-infra/github-actions/post-approval-changes@03b8a7dffd1205e061f0bee949024ebefc2a6592
- uses: angular/dev-infra/github-actions/post-approval-changes@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: angular/dev-infra/github-actions/google-internal-tests@03b8a7dffd1205e061f0bee949024ebefc2a6592
- uses: angular/dev-infra/github-actions/google-internal-tests@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
run-tests-guide-url: http://go/angular-g3sync-start
github-token: ${{ secrets.GITHUB_TOKEN }}
+34
View File
@@ -0,0 +1,34 @@
name: Manual jobs
on:
workflow_dispatch:
inputs: {}
jobs:
# Bazel saucelabs job resides in `manual.yml` because it's currently unstable, but
# kept as "runnable" for debugging/stabilization effort purposes.
bazel-saucelabs:
runs-on: ubuntu-latest-4core
env:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Set up Sauce Tunnel Daemon
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
- name: Run all saucelabs bazel tests
run: |
TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)')
yarn bazel test --config=saucelabs --jobs=$JOBS ${TESTS}
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
status:
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/unified-status-check@03b8a7dffd1205e061f0bee949024ebefc2a6592
- uses: angular/dev-infra/github-actions/unified-status-check@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+24 -55
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Install node modules
@@ -39,7 +39,7 @@ jobs:
- name: Check code format
run: yarn ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
- name: Check Package Licenses
uses: angular/dev-infra/github-actions/linting/licenses@56966c765e97f463c4795fac85d46543d1e6b342
uses: angular/dev-infra/github-actions/linting/licenses@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
@@ -47,13 +47,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Run unit tests
@@ -65,13 +65,13 @@ jobs:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile --network-timeout 100000
- name: Run CI tests for framework
@@ -83,13 +83,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile --network-timeout 100000
- name: Run CI tests for framework
@@ -105,24 +105,26 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev in fast mode to ensure it continues to work
run: yarn bazel build //adev:build --fast_adev --config=release
- name: Run tests
run: yarn bazel test //adev:test
run: yarn bazel build //adev:build --config=release
# TODO: re-enable tests once the next release is shipped
# Tests are broken because of https://github.com/angular/angular/issues/54858
# - name: Run tests
# run: yarn bazel test //adev/...
zone-js:
runs-on:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
node-module-directories: |
@@ -130,9 +132,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@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/setup@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/bazel/configure-remote@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
- name: Install node modules
run: yarn install --frozen-lockfile
- run: |
@@ -169,42 +171,9 @@ jobs:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
cache-node-modules: true
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@03b8a7dffd1205e061f0bee949024ebefc2a6592
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@03b8a7dffd1205e061f0bee949024ebefc2a6592
- 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
# specific tests which are reliant on such generated fixtures is not an option as SystemJS
# in the Saucelabs legacy job always fetches referenced files, even if the imports would be
# guarded by an check to skip in the Saucelabs legacy job. We should be good running such
# test in all supported browsers on Saucelabs anyway until this job can be removed.
- name: Preparing Bazel-generated fixtures required in legacy tests
run: |
yarn bazel build //packages/core/test:downleveled_es5_fixture //packages/common/locales
# Needed for the ES5 downlevel reflector test in `packages/core/test/reflection`.
mkdir -p dist/legacy-test-out/core/test/reflection/
cp dist/bin/packages/core/test/reflection/es5_downleveled_inheritance_fixture.js \
dist/legacy-test-out/core/test/reflection/es5_downleveled_inheritance_fixture.js
# Locale files are needed for i18n tests running within Saucelabs. These are added
# directly as sources so that the TypeScript compilation of `/packages/tsconfig.json`
# can succeed. Note that the base locale and currencies files are checked-in, so
# we do not need to re-generate those through Bazel.
mkdir -p packages/common/locales/extra
cp dist/bin/packages/common/locales/*.ts packages/common/locales
cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra
- name: Build bundle of tests to run on Saucelabs
run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs
- name: Wait and confirm Saucelabs tunnel has connected
run: ./tools/saucelabs/sauce-service.sh ready-wait
timeout-minutes: 3
- name: Running tests on Saucelabs.
run: KARMA_WEB_TEST_MODE=SL_REQUIRED yarn karma start ./karma-js.conf.js --single-run
- name: Stop Saucelabs tunnel service
run: ./tools/saucelabs/sauce-service.sh stop
- uses: ./.github/actions/saucelabs-legacy
+3 -3
View File
@@ -25,7 +25,7 @@ jobs:
steps:
- name: 'Checkout code'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
with:
persist-credentials: false
@@ -39,7 +39,7 @@ jobs:
# Upload the results as artifacts.
- name: 'Upload artifact'
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
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@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
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@03b8a7dffd1205e061f0bee949024ebefc2a6592
uses: angular/dev-infra/github-actions/create-pr-for-changes@8d3cb7b9e1b3edd1dc168376689d27407d2dfa7a
with:
branch-prefix: update-cli-help
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
+4 -4
View File
@@ -57,7 +57,7 @@
version: 3
availability:
users_unavailable: []
users_unavailable: ['atscott']
# Meta field that goes unused by PullApprove to allow for defining aliases to be
# used throughout the config.
@@ -372,7 +372,7 @@ groups:
<<: *defaults
conditions:
- >
contains_any_globs(files.exclude('packages/compiler-cli/*').exclude('packages/language-service/*').exclude('packages/service-worker/*'), [
contains_any_globs(files.exclude('packages/compiler-cli/*').exclude('packages/language-service/*').exclude('packages/service-worker/*').exclude('packages/core/schematics/*'), [
'packages/**/testing/**/{*,.*}',
])
reviewers:
@@ -613,6 +613,7 @@ groups:
- devversion
- atscott
- pkozlowski-opensource
- MarkTechson
- ~JeanMeche
# =========================================================
@@ -632,7 +633,7 @@ groups:
- dgp1130
- josephperrott
- mgechev
- twerske
- MarkTechson
- ~JeanMeche
# =========================================================
@@ -827,7 +828,6 @@ groups:
- eduhmc # Eduardo Huerta-Mercado
- ehlemur # Edward Lesmes
- ellenyuan # Ellen Yuan
- jatraman # Jatin Ramanathan
- jesse-good # Jesse Costello-Good
- mturco # Matt Turco
- neonstalwart # Ben Hockey
+3374 -2129
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -89,7 +89,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
We cannot accept code without a signed CLA.
Make sure you author all contributed Git commits with email address associated with your CLA signature.
4. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the angular/angular repo.
4. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the [angular/angular](https://github.com/angular/angular/fork) repo.
5. In your forked repository, make your changes in a new git branch:
@@ -207,7 +207,7 @@ To ensure consistency throughout the source code, keep these rules in mind as yo
* All public API methods **must be documented**.
* We follow [Google's JavaScript Style Guide][js-style-guide], but wrap all code at **100 characters**.
An automated formatter is available, see [DEVELOPER.md](contributing-docs/building-and-testing-angular#formatting-your-source-code).
An automated formatter is available, see [DEVELOPER.md](contributing-docs/building-and-testing-angular.md#formatting-your-source-code).
## <a name="commit"></a> Commit Message Format
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2010-2024 Google LLC. https://angular.io/license
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+15 -3
View File
@@ -143,10 +143,10 @@ cldr_xml_data_repository(
# sass rules
http_archive(
name = "io_bazel_rules_sass",
sha256 = "cd83736ea65d0df064283aea5922dbaf132dd2b3aa54e7151aae7edaa9572c3e",
strip_prefix = "rules_sass-83022b98114c07e9588089c7fe8f76bc0262c7e7",
sha256 = "b00f13a280fc60ef2a1d142b31c629075bac841c980a0c3462190f338e5d584a",
strip_prefix = "rules_sass-6e23fb1351f392f7fe7a07038cd537770fe97742",
urls = [
"https://github.com/bazelbuild/rules_sass/archive/83022b98114c07e9588089c7fe8f76bc0262c7e7.zip",
"https://github.com/bazelbuild/rules_sass/archive/6e23fb1351f392f7fe7a07038cd537770fe97742.zip",
],
)
@@ -181,3 +181,15 @@ http_archive(
strip_prefix = "sc-4.8.2-osx",
url = "https://saucelabs.com/downloads/sc-4.8.2-osx.zip",
)
yarn_install(
name = "npm_ts_versions",
data = [
YARN_LABEL,
"//:.yarnrc",
],
exports_directories_only = False,
package_json = "//packages/core/schematics/migrations/signal-migration/test/ts-versions:package.json",
yarn = YARN_LABEL,
yarn_lock = "//packages/core/schematics/migrations/signal-migration/test/ts-versions:yarn.lock",
)
+16 -20
View File
@@ -1,4 +1,5 @@
load("@npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
load("@npm//@angular/build-tooling/bazel/http-server:index.bzl", "http_server")
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")
@@ -113,27 +114,27 @@ copy_to_bin(
)
bool_flag(
name = "fast_build_mode",
name = "prerender_adev",
build_setting_default = False,
)
config_setting(
name = "fast",
name = "no_prerender",
flag_values = {
":fast_build_mode": "true",
":prerender_adev": "false",
},
)
config_setting(
name = "full",
name = "prerender",
flag_values = {
":fast_build_mode": "false",
":prerender_adev": "true",
},
)
config_based_architect_flags = select({
":fast": ["--no-prerender"],
":full": ["--prerender"],
":no_prerender": ["--no-prerender"],
":prerender": ["--prerender"],
})
architect(
@@ -146,6 +147,9 @@ architect(
data = ensure_local_package_deps(APPLICATION_DEPS) + APPLICATION_ASSETS + [
":application_files_bin",
],
env = {
"NG_BUILD_OPTIMIZE_CHUNKS": "1",
},
# Network is required to inline fonts.
exec_properties = ENABLE_NETWORK,
output_dir = True,
@@ -154,21 +158,13 @@ architect(
],
)
architect(
http_server(
name = "serve",
args = [
"angular-dev:serve",
"--poll=1000",
"--live-reload",
"--watch",
],
chdir = package_name(),
data = ensure_local_package_deps(APPLICATION_DEPS) + APPLICATION_ASSETS + [
":application_files_bin",
],
tags = [
"no-remote-exec",
additional_root_paths = [
"angular/adev/build/browser",
],
enable_dev_ui = True,
deps = [":build"],
)
architect_test(
+1
View File
@@ -22,6 +22,7 @@ ts_library(
"//adev/shared-docs/components/slide-toggle",
"//adev/shared-docs/components/table-of-contents",
"//adev/shared-docs/components/text-field",
"//adev/shared-docs/components/top-level-banner",
"//adev/shared-docs/components/viewers",
],
)
@@ -9,13 +9,12 @@
import {ChangeDetectionStrategy, Component, OnInit, inject, signal} from '@angular/core';
import {NavigationState} from '../../services/index';
import {NavigationItem} from '../../interfaces/index';
import {NgFor, NgIf} from '@angular/common';
import {RouterLink} from '@angular/router';
@Component({
selector: 'docs-breadcrumb',
standalone: true,
imports: [NgIf, NgFor, RouterLink],
imports: [RouterLink],
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -7,7 +7,6 @@
*/
import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
import {NgIf} from '@angular/common';
import {LOCAL_STORAGE} from '../../providers/index';
import {setCookieConsent} from '../../utils';
@@ -21,7 +20,6 @@ export const STORAGE_KEY = 'docs-accepts-cookies';
@Component({
selector: 'docs-cookie-popup',
standalone: true,
imports: [NgIf],
templateUrl: './cookie-popup.component.html',
styleUrls: ['./cookie-popup.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -18,6 +18,7 @@ ng_module(
"//adev/shared-docs/components/navigation-list:__pkg__",
"//adev/shared-docs/components/table-of-contents:__pkg__",
"//adev/shared-docs/components/text-field:__pkg__",
"//adev/shared-docs/components/top-level-banner:__pkg__",
"//adev/shared-docs/components/viewers:__pkg__",
],
deps = [
+1
View File
@@ -15,3 +15,4 @@ export * from './table-of-contents/table-of-contents.component';
export * from './text-field/text-field.component';
export * from './icon/icon.component';
export * from './search-dialog/search-dialog.component';
export * from './top-level-banner/top-level-banner.component';
@@ -22,6 +22,7 @@ ng_module(
"//adev/shared-docs/interfaces",
"//adev/shared-docs/pipes",
"//adev/shared-docs/services",
"//packages/common",
"//packages/core",
"//packages/forms",
"//packages/router",
@@ -42,6 +43,7 @@ ts_library(
deps = [
":search-dialog",
"//adev/shared-docs/components/algolia-icon",
"//adev/shared-docs/interfaces",
"//adev/shared-docs/providers",
"//adev/shared-docs/services",
"//adev/shared-docs/testing",
@@ -10,65 +10,62 @@
></docs-text-field>
@if (searchResults() && searchResults()!.length > 0) {
<ul class="docs-search-results docs-mini-scroll-track">
@for (result of searchResults(); track result.objectID) {
<li docsSearchItem [item]="result">
@if (result.url) {
<a [routerLink]="'/' + result.url | relativeLink: 'pathname'" [fragment]="result.url | relativeLink: 'hash'">
<div>
<div class="docs-result-icon-and-type">
<!-- Icon -->
<span class="docs-search-result-icon" aria-hidden="true">
@if (result.hierarchy?.lvl0 === 'Docs') {
<i role="presentation" class="material-symbols-outlined docs-icon-small">
description
</i>
} @else if (result.hierarchy?.lvl0 === 'Tutorials') {
<i role="presentation" class="material-symbols-outlined docs-icon-small">code</i>
} @else if (result.hierarchy?.lvl0 === 'Reference') {
<i role="presentation" class="material-symbols-outlined docs-icon-small">
description
</i>
}
</span>
<!-- Results type -->
<span class="docs-search-results__type">{{ result.hierarchy?.lvl1 }}</span>
<ul class="docs-search-results docs-mini-scroll-track">
@for (result of searchResults(); track result.objectID) {
<li docsSearchItem [item]="result">
<a
[routerLink]="'/' + result.url | relativeLink: 'pathname'"
[fragment]="result.url | relativeLink: 'hash'"
>
<div>
<div class="docs-result-icon-and-type">
<!-- Icon -->
<span class="docs-search-result-icon" aria-hidden="true">
<i role="presentation" class="material-symbols-outlined docs-icon-small">
{{ result.hierarchy.lvl0 === 'Tutorials' ? 'code' : 'description'}}
</i>
</span>
<!-- Results type -->
<span class="docs-search-results__type">
@let snippet = result._snippetResult.hierarchy?.lvl1?.value ?? '';
<ng-container
[ngTemplateOutlet]="highlightSnippet"
[ngTemplateOutletContext]="{snippet}"
></ng-container>
</span>
</div>
@let content = result._snippetResult.content;
@let hierarchy = result._snippetResult.hierarchy;
@if (content || hierarchy?.lvl2 || hierarchy?.lvl3 || hierarchy?.lvl4) {
<span class="docs-search-results__type docs-search-results__lvl2">
@let snippet = getBestSnippetForMatch(result);
<ng-container
[ngTemplateOutlet]="highlightSnippet"
[ngTemplateOutletContext]="{snippet}"
></ng-container>
</span>
}
</div>
<!-- Hide level 2 if level 3 exists -->
<!-- Level 2 -->
@if (result.hierarchy?.lvl2 && !result.hierarchy?.lvl3) {
<span class="docs-search-results__type docs-search-results__lvl2">
{{ result.hierarchy?.lvl2 }}
</span>
}
<!-- Level 3 -->
@if (result.hierarchy?.lvl3) {
<span class="docs-search-results__type docs-search-results__lvl3">
{{ result.hierarchy?.lvl3 }}
</span>
}
</div>
<!-- Page title -->
<span class="docs-result-page-title">{{ result.hierarchy?.lvl0 }}</span>
</a>
<!-- Page title -->
<span class="docs-result-page-title">{{ result.hierarchy?.lvl0 }}</span>
</a>
</li>
}
</li>
}
</ul>
</ul>
} @else {
<div class="docs-search-results docs-mini-scroll-track">
@if (searchResults() === undefined) {
<div class="docs-search-results__start-typing">
<span>Start typing to see results</span>
<div class="docs-search-results docs-mini-scroll-track">
@if (searchResults() === undefined) {
<div class="docs-search-results__start-typing">
<span>Start typing to see results</span>
</div>
} @else if (searchResults()?.length === 0) {
<div class="docs-search-results__no-results">
<span>No results found</span>
</div>
}
</div>
} @else if (searchResults()?.length === 0) {
<div class="docs-search-results__no-results">
<span>No results found</span>
</div>
}
</div>
}
<div class="docs-algolia">
@@ -79,3 +76,14 @@
</div>
</div>
</dialog>
<ng-template #highlightSnippet let-snippet="snippet">
@let parts = splitMarkedText(snippet);
@for (part of parts; track $index) {
@if (part.highlight) {
<mark>{{part.text}}</mark>
} @else {
<span>{{part.text}}</span>
}
}
</ng-template>
@@ -48,6 +48,14 @@ dialog {
padding-inline-end: 1rem;
padding-block: 0.25rem;
mark {
background: #e62600;
background: var(--red-to-orange-horizontal-gradient);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
a {
color: var(--secondary-contrast);
display: flex;
@@ -17,6 +17,7 @@ import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
import {RouterTestingModule} from '@angular/router/testing';
import {Router} from '@angular/router';
import {provideExperimentalZonelessChangeDetection} from '@angular/core';
import {SearchResult} from '../../interfaces';
describe('SearchDialog', () => {
let fixture: ComponentFixture<SearchDialog>;
@@ -135,6 +136,9 @@ const fakeSearchResults = [
'lvl6': null,
},
'objectID': 'fakeObjectId1',
_snippetResult: {},
type: '',
content: null,
},
{
'url': 'https://angular.dev/fakeUrl2#h1',
@@ -148,5 +152,8 @@ const fakeSearchResults = [
'lvl6': null,
},
'objectID': 'fakeObjectId2',
type: '',
content: null,
_snippetResult: {},
},
];
] satisfies SearchResult[];
@@ -20,6 +20,7 @@ import {
viewChild,
viewChildren,
} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {WINDOW} from '../../providers/index';
import {ClickOutside} from '../../directives/index';
@@ -34,6 +35,7 @@ import {Router, RouterLink} from '@angular/router';
import {filter, fromEvent} from 'rxjs';
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
import {RelativeLink} from '../../pipes/relative-link.pipe';
import {SearchResult, SnippetResult} from '../../interfaces';
@Component({
selector: 'docs-search-dialog',
@@ -47,6 +49,7 @@ import {RelativeLink} from '../../pipes/relative-link.pipe';
AlgoliaIcon,
RelativeLink,
RouterLink,
NgTemplateOutlet,
],
templateUrl: './search-dialog.component.html',
styleUrls: ['./search-dialog.component.scss'],
@@ -104,6 +107,46 @@ export class SearchDialog implements OnDestroy {
});
}
splitMarkedText(snippet: string): Array<{highlight: boolean; text: string}> {
const parts: Array<{highlight: boolean; text: string}> = [];
while (snippet.indexOf('<ɵ>') !== -1) {
const beforeMatch = snippet.substring(0, snippet.indexOf('<ɵ>'));
const match = snippet.substring(snippet.indexOf('<ɵ>') + 3, snippet.indexOf('</ɵ>'));
parts.push({highlight: false, text: beforeMatch});
parts.push({highlight: true, text: match});
snippet = snippet.substring(snippet.indexOf('</ɵ>') + 4);
}
parts.push({highlight: false, text: snippet});
return parts;
}
getBestSnippetForMatch(result: SearchResult): string {
// if there is content, return it
if (result._snippetResult.content !== undefined) {
return result._snippetResult.content.value;
}
const hierarchy = result._snippetResult.hierarchy;
if (hierarchy === undefined) {
return '';
}
function matched(snippet: SnippetResult | undefined) {
return snippet?.matchLevel !== undefined && snippet.matchLevel !== 'none';
}
// return the most specific subheader match
if (matched(hierarchy.lvl4)) {
return hierarchy.lvl4!.value;
}
if (matched(hierarchy.lvl3)) {
return hierarchy.lvl3!.value;
}
if (matched(hierarchy.lvl2)) {
return hierarchy.lvl2!.value;
}
// if no subheader matched the query, fall back to just returning the most specific one
return hierarchy.lvl3?.value ?? hierarchy.lvl2?.value ?? '';
}
ngOnDestroy(): void {
this.keyManager.destroy();
}
@@ -6,8 +6,14 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {NgFor, NgIf} from '@angular/common';
import {ChangeDetectionStrategy, Component, Input, computed, inject} from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
DestroyRef,
Input,
computed,
inject,
} from '@angular/core';
import {RouterLink} from '@angular/router';
import {TableOfContentsLevel} from '../../interfaces/index';
import {TableOfContentsLoader} from '../../services/table-of-contents-loader.service';
@@ -20,7 +26,7 @@ import {IconComponent} from '../icon/icon.component';
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './table-of-contents.component.html',
styleUrls: ['./table-of-contents.component.scss'],
imports: [NgIf, NgFor, RouterLink, IconComponent],
imports: [RouterLink, IconComponent],
})
export class TableOfContents {
// Element that contains the content from which the Table of Contents is built
@@ -28,6 +34,8 @@ export class TableOfContents {
private readonly scrollSpy = inject(TableOfContentsScrollSpy);
private readonly tableOfContentsLoader = inject(TableOfContentsLoader);
private readonly destroyRef = inject(DestroyRef);
tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems;
activeItemId = this.scrollSpy.activeItemId;
@@ -36,7 +44,7 @@ export class TableOfContents {
ngAfterViewInit() {
this.tableOfContentsLoader.buildTableOfContent(this.contentSourceElement);
this.scrollSpy.startListeningToScroll(this.contentSourceElement);
this.scrollSpy.startListeningToScroll(this.contentSourceElement, this.destroyRef);
}
scrollToTop(): void {
@@ -0,0 +1,49 @@
load("//tools:defaults.bzl", "karma_web_test_suite", "ng_module", "ts_library")
load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
package(default_visibility = ["//visibility:private"])
ng_module(
name = "top-level-banner",
srcs = [
"top-level-banner.component.ts",
],
assets = [
":top-level-banner.component.css",
"top-level-banner.component.html",
],
visibility = [
"//adev/shared-docs/components:__pkg__",
],
deps = [
"//adev/shared-docs/components/icon",
"//adev/shared-docs/directives",
"//adev/shared-docs/providers",
"//packages/common",
"//packages/core",
],
)
sass_binary(
name = "style",
src = "top-level-banner.component.scss",
)
ts_library(
name = "test_lib",
testonly = True,
srcs = glob(
["*.spec.ts"],
),
deps = [
":top-level-banner",
"//adev/shared-docs/providers",
"//packages/core",
"//packages/core/testing",
],
)
karma_web_test_suite(
name = "test",
deps = [":test_lib"],
)
@@ -0,0 +1,17 @@
@if (!hasClosed()) {
@if (link()) {
<a [href]="link()" class="docs-top-level-banner">
<h1 tabindex="-1" class="docs-top-level-banner-cta background">{{ text() }}</h1>
<h1 tabindex="0" class="docs-top-level-banner-cta shimmer">{{ text() }}</h1>
</a>
} @else {
<div class="docs-top-level-banner">
<h1 tabindex="-1" class="docs-top-level-banner-cta background">{{ text() }}</h1>
<h1 tabindex="0" class="docs-top-level-banner-cta shimmer">{{ text() }}</h1>
</div>
}
<button class="docs-top-level-banner-close" type="button" (click)="close()">
<docs-icon class="docs-icon_high-contrast">close</docs-icon>
</button>
}
@@ -0,0 +1,134 @@
:host {
&:not(:empty) {
z-index: 50;
position: fixed;
height: 2rem;
width: 100vw;
border-bottom: 1px solid var(--septenary-contrast);
text-align: center;
align-content: center;
backdrop-filter: blur(16px);
background-color: color-mix(in srgb, var(--page-background) 70%, transparent);
}
a.docs-top-level-banner {
width: 100%;
display: inherit;
}
h1.docs-top-level-banner-cta {
display: inline;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.875rem;
margin: 0;
width: fit-content;
font-weight: 500;
&.background {
color: var(--tertiary-contrast);
}
&:not(.background) {
color: transparent;
&::after {
content: '';
position: absolute;
width: 100%;
height: 1px;
bottom: -2px;
left: 0;
background: var(--tertiary-contrast);
transform: scaleX(0);
transform-origin: bottom right;
@media (prefers-reduced-motion: no-preference) {
transition: transform 0.3s ease;
}
}
&:hover {
&::after {
transform: scaleX(1);
transform-origin: bottom left;
}
}
}
}
.docs-top-level-banner-close {
position: absolute;
top: 0.25rem;
right: 0.5rem;
color: var(--tertiary-contrast);
}
}
.shimmer {
background: var(--red-to-pink-to-purple-horizontal-gradient);
@media (prefers-reduced-motion: no-preference) {
background-repeat: no-repeat;
-webkit-background-size: 125px 100%;
-moz-background-size: 125px 100%;
background-size: 125px 100%;
-webkit-background-clip: text;
-moz-background-clip: text;
background-clip: text;
-webkit-animation-name: shimmer;
-moz-animation-name: shimmer;
animation-name: shimmer;
-webkit-animation-duration: 10s;
-moz-animation-duration: 10s;
animation-duration: 10s;
-webkit-animation-iteration-count: infinite;
-moz-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
}
@-moz-keyframes shimmer {
0% {
background-position: top left;
background-position-x: -150px;
}
100% {
background-position: top right;
background-position-x: 500px;
}
}
@-webkit-keyframes shimmer {
0% {
background-position: top left;
background-position-x: -150px;
}
100% {
background-position: top right;
background-position-x: 500px;
}
}
@-o-keyframes shimmer {
0% {
background-position: top left;
background-position-x: -150px;
}
100% {
background-position: top right;
background-position-x: 500px;
}
}
@keyframes shimmer {
0% {
background-position: top left;
background-position-x: -150px;
}
100% {
background-position: top right;
background-position-x: 500px;
}
}
@@ -0,0 +1,100 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {STORAGE_KEY_PREFIX, TopLevelBannerComponent} from './top-level-banner.component';
import {LOCAL_STORAGE, WINDOW} from '../../providers';
describe('TopLevelBannerComponent', () => {
let component: TopLevelBannerComponent;
let fixture: ComponentFixture<TopLevelBannerComponent>;
let mockLocalStorage: jasmine.SpyObj<Storage>;
const EXAMPLE_TEXT = 'Click Here';
const EXAMPLE_LINK = 'https://example.com';
const EXAMPLE_ID = 'banner-id';
beforeEach(async () => {
mockLocalStorage = jasmine.createSpyObj('Storage', ['getItem', 'setItem']);
fixture = TestBed.configureTestingModule({
providers: [
{provide: LOCAL_STORAGE, useValue: mockLocalStorage},
{provide: WINDOW, useValue: {location: {origin: ''}}},
],
}).createComponent(TopLevelBannerComponent);
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
fixture.componentRef.setInput('id', EXAMPLE_ID);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should render an anchor element when link is provided', () => {
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
fixture.componentRef.setInput('link', EXAMPLE_LINK);
fixture.detectChanges();
const bannerElement = fixture.nativeElement.querySelector('a.docs-top-level-banner');
expect(bannerElement).toBeTruthy();
expect(bannerElement.getAttribute('href')).toBe(EXAMPLE_LINK);
expect(bannerElement.textContent).toContain(EXAMPLE_TEXT);
});
it('should render a div element when link is not provided', () => {
const EXAMPLE_TEXT = 'No Link Available';
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
fixture.detectChanges();
const bannerElement = fixture.nativeElement.querySelector('div.docs-top-level-banner');
expect(bannerElement).toBeTruthy();
expect(bannerElement.textContent).toContain(EXAMPLE_TEXT);
});
it('should correctly render the text input', () => {
const EXAMPLE_TEXT = 'Lorem ipsum dolor...';
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
fixture.detectChanges();
const bannerElement = fixture.nativeElement.querySelector('.docs-top-level-banner-cta');
expect(bannerElement).toBeTruthy();
expect(bannerElement.textContent).toBe(EXAMPLE_TEXT);
});
it('should set hasClosed to true if the banner was closed before', () => {
mockLocalStorage.getItem.and.returnValue('true');
component.ngOnInit();
expect(component.hasClosed()).toBeTrue();
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`);
});
it('should set hasClosed to false if the banner was not closed before', () => {
mockLocalStorage.getItem.and.returnValue('false');
component.ngOnInit();
expect(component.hasClosed()).toBeFalse();
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`);
});
it('should set hasClosed to false if accessing localStorage throws an error', () => {
mockLocalStorage.getItem.and.throwError('Local storage error');
component.ngOnInit();
expect(component.hasClosed()).toBeFalse();
});
it('should set the banner as closed in localStorage and update hasClosed', () => {
component.close();
expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`,
'true',
);
expect(component.hasClosed()).toBeTrue();
});
});
@@ -0,0 +1,69 @@
import {ChangeDetectionStrategy, Component, inject, input, OnInit, signal} from '@angular/core';
import {ExternalLink} from '../../directives';
import {LOCAL_STORAGE} from '../../providers';
import {IconComponent} from '../icon/icon.component';
export const STORAGE_KEY_PREFIX = 'docs-was-closed-top-banner-';
@Component({
selector: 'docs-top-level-banner',
standalone: true,
imports: [ExternalLink, IconComponent],
templateUrl: './top-level-banner.component.html',
styleUrl: './top-level-banner.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TopLevelBannerComponent implements OnInit {
private readonly localStorage = inject(LOCAL_STORAGE);
/**
* Unique identifier for the banner. This ID is required to ensure that
* the state of the banner (e.g., whether it has been closed) is tracked
* separately for different events or instances. Without a unique ID,
* closing one banner could inadvertently hide other banners for different events.
*/
id = input.required<string>();
// Optional URL link that the banner should navigate to when clicked.
link = input<string>();
// Text content to be displayed in the banner.
text = input.required<string>();
// Optional expiry date. Setting the default expiry as a future date so we
// don't have to deal with undefined signal values.
expiry = input(new Date('3000-01-01'), {transform: parseDate});
// Whether the user has closed the banner or the survey has expired.
hasClosed = signal<boolean>(false);
ngOnInit(): void {
const expired = Date.now() > this.expiry().getTime();
// Needs to be in a try/catch, because some browsers will
// throw when using `localStorage` in private mode.
try {
this.hasClosed.set(
this.localStorage?.getItem(this.getBannerStorageKey()) === 'true' || expired,
);
} catch {
this.hasClosed.set(false);
}
}
close(): void {
this.localStorage?.setItem(this.getBannerStorageKey(), 'true');
this.hasClosed.set(true);
}
private getBannerStorageKey(): string {
return `${STORAGE_KEY_PREFIX}${this.id()}`;
}
}
const parseDate = (inputDate: string | Date): Date => {
if (inputDate instanceof Date) {
return inputDate;
}
const outputDate = new Date(inputDate);
if (isNaN(outputDate.getTime())) {
throw new Error(`Invalid date string: ${inputDate}`);
}
return outputDate;
};
@@ -75,7 +75,6 @@ export class DocViewer implements OnChanges {
private readonly elementRef = inject(ElementRef);
private readonly location = inject(Location);
private readonly navigationState = inject(NavigationState);
private readonly platformId = inject(PLATFORM_ID);
private readonly router = inject(Router);
private readonly viewContainer = inject(ViewContainerRef);
private readonly environmentInjector = inject(EnvironmentInjector);
@@ -86,6 +85,8 @@ export class DocViewer implements OnChanges {
private animateContent = false;
private readonly pendingRenderTasks = inject(PendingTasks);
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
private countOfExamples = 0;
async ngOnChanges(changes: SimpleChanges): Promise<void> {
@@ -97,11 +98,10 @@ export class DocViewer implements OnChanges {
}
async renderContentsAndRunClientSetup(content?: string): Promise<void> {
const isBrowser = isPlatformBrowser(this.platformId);
const contentContainer = this.elementRef.nativeElement;
if (content) {
if (isBrowser && !(this.document as any).startViewTransition) {
if (this.isBrowser && !(this.document as any).startViewTransition) {
// Apply a special class to the host node to trigger animation.
// Note: when a page is hydrated, the `content` would be empty,
// so we don't trigger an animation to avoid a content flickering
@@ -112,7 +112,7 @@ export class DocViewer implements OnChanges {
contentContainer.innerHTML = content;
}
if (isBrowser) {
if (this.isBrowser) {
// First we setup event listeners on the HTML we just loaded.
// We want to do this before things like the example viewers are loaded.
this.setupAnchorListeners(contentContainer);
@@ -301,6 +301,14 @@ export class DocViewer implements OnChanges {
// during SSG.
this.appRef.attachView(componentRef.hostView);
// This is wrapped with `isBrowser` in for hydration purposes.
if (this.isBrowser) {
// The `docs-viewer` may be rendered multiple times when navigating
// between pages, which will create new components that need to be
// destroyed for gradual cleanup.
this.destroyRef.onDestroy(() => componentRef.destroy());
}
return componentRef;
}
+27 -2
View File
@@ -6,14 +6,39 @@
* found in the LICENSE file at https://angular.dev/license
*/
export interface SnippetResult {
value: string;
matchLevel: 'none' | 'full' | string;
}
/* The interface represents Algolia search result item. */
export interface SearchResult {
/* The url link to the search result page */
url?: string;
url: string;
/* The hierarchy of the item */
hierarchy?: Hierarchy;
hierarchy: Hierarchy;
/* The unique id of the search result item */
objectID: string;
/**
* The type of the result. A content result will have
* matched the content. A result of type 'lvl#' may have i
* matched a lvl above it. For example, a type 'lvl3' may be
* included in results because its 'lvl2' header matched the query.
*/
type: string;
/** Documentation content (not headers) */
content: string | null;
/** Snippets of the matched text */
_snippetResult: {
hierarchy?: {
lvl0?: SnippetResult;
lvl1?: SnippetResult;
lvl2?: SnippetResult;
lvl3?: SnippetResult;
lvl4?: SnippetResult;
};
content?: SnippetResult;
};
}
/* The hierarchy of the item */
+3 -3
View File
@@ -19,9 +19,9 @@
"fast-glob": "~3.3.2",
"fflate": "^0.8.2",
"html-entities": "~2.5.2",
"jsdom": "~24.1.0",
"marked": "~12.0.2",
"mermaid": "^10.8.0",
"jsdom": "~25.0.0",
"marked": "~14.0.0",
"mermaid": "^11.0.0",
"shiki": "^1.10.3"
},
"exports": {
@@ -13,6 +13,12 @@ def _extract_api_to_json(ctx):
# Pass the module_name for the extracted APIs. This will be something like "@angular/core".
args.add(ctx.attr.module_name)
# Pass the module_label for the extracted APIs, This is something like core for "@angular/core".
args.add(ctx.attr.module_label)
# Pass the set of private modules that should not be included in the API reference.
args.add_joined(ctx.attr.private_modules, join_with = ",")
# Pass the entry_point for from which to extract public symbols.
args.add(ctx.file.entry_point)
@@ -74,6 +80,9 @@ extract_api_to_json = rule(
mandatory = True,
allow_single_file = True,
),
"private_modules": attr.string_list(
doc = """List of private modules that should not be included in the API symbol linking""",
),
"import_map": attr.label_keyed_string_dict(
doc = """Map of import path to the index.ts file for that import""",
allow_files = True,
@@ -82,6 +91,9 @@ extract_api_to_json = rule(
doc = """JS Module name to be used for the extracted symbols""",
mandatory = True,
),
"module_label": attr.string(
doc = """Module label to be used for the extracted symbols. To be used as display name, for example in API docs""",
),
"extra_entries": attr.label_list(
doc = """JSON files that contain extra entries to append to the final collection.""",
allow_files = True,
@@ -1,7 +1,15 @@
import {readFileSync, writeFileSync} from 'fs';
import path from 'path';
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
import {NgtscProgram, CompilerOptions, createCompilerHost, DocEntry} from '@angular/compiler-cli';
import {
NgtscProgram,
CompilerOptions,
createCompilerHost,
DocEntry,
EntryCollection,
InterfaceEntry,
ClassEntry,
} from '@angular/compiler-cli';
import ts from 'typescript';
function main() {
@@ -10,6 +18,8 @@ function main() {
const [
moduleName,
moduleLabel,
serializedPrivateModules,
entryPointExecRootRelativePath,
srcs,
outputFilenameExecRootRelativePath,
@@ -17,6 +27,8 @@ function main() {
extraEntriesSrcs,
] = rawParamLines;
const privateModules = new Set(serializedPrivateModules.split(','));
// The path map is a serialized JSON map of import path to index.ts file.
// For example, {'@angular/core': 'path/to/some/index.ts'}
const pathMap = JSON.parse(serializedPathMapWithExecRootRelativePaths) as Record<string, string>;
@@ -54,15 +66,45 @@ function main() {
return result.concat(JSON.parse(readFileSync(path, {encoding: 'utf8'})) as DocEntry[]);
}, []);
const extractedEntries = program.getApiDocumentation(entryPointExecRootRelativePath);
const apiDoc = program.getApiDocumentation(entryPointExecRootRelativePath, privateModules);
const extractedEntries = apiDoc.entries;
const combinedEntries = extractedEntries.concat(extraEntries);
const normalized = moduleName.replace('@', '').replace(/[\/]/g, '_');
const output = JSON.stringify({
moduleLabel: moduleLabel || moduleName,
moduleName: moduleName,
normalizedModuleName: normalized,
entries: combinedEntries,
});
symbols: [
// Symbols referenced, originating from other packages
...apiDoc.symbols.entries(),
// Exported symbols from the current package
...apiDoc.entries.map((entry) => [entry.name, moduleName]),
// Also doing it for every member of classes/interfaces
...apiDoc.entries.flatMap((entry) => [
[entry.name, moduleName],
...getEntriesFromMembers(entry).map((member) => [member, moduleName]),
]),
],
} as EntryCollection);
writeFileSync(outputFilenameExecRootRelativePath, output, {encoding: 'utf8'});
}
function getEntriesFromMembers(entry: DocEntry): string[] {
if (!hasMembers(entry)) {
return [];
}
return entry.members.map((member) => `${entry.name}.${member.name}`);
}
function hasMembers(entry: DocEntry): entry is InterfaceEntry | ClassEntry {
return 'members' in entry;
}
main();
@@ -14,6 +14,7 @@ extract_api_to_json(
},
module_name = "@angular/core",
output_name = "api.json",
private_modules = [""],
)
extract_api_to_json(
@@ -31,6 +32,7 @@ extract_api_to_json(
},
module_name = "@angular/core",
output_name = "extra_api.json",
private_modules = [""],
)
filegroup(
@@ -1,18 +1,20 @@
load("//adev/shared-docs/pipeline/api-gen/extraction:extract_api_to_json.bzl", "extract_api_to_json")
load("//adev/shared-docs/pipeline/api-gen/rendering:render_api_to_html.bzl", "render_api_to_html")
def generate_api_docs(name, module_name, entry_point, srcs, import_map = {}, extra_entries = []):
def generate_api_docs(name, module_name, entry_point, srcs, private_modules = [""], module_label = None, import_map = {}, extra_entries = []):
"""Generates API documentation reference pages for the given sources."""
json_outfile = name + "_api.json"
extract_api_to_json(
name = name + "_extraction",
module_name = module_name,
module_label = module_label,
entry_point = entry_point,
srcs = srcs,
output_name = json_outfile,
import_map = import_map,
extra_entries = extra_entries,
private_modules = private_modules,
)
render_api_to_html(
@@ -1,11 +1,5 @@
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
import type {DocEntry, JsDocTagEntry} from '@angular/compiler-cli';
/** The JSON data file format for extracted API reference info. */
export interface EntryCollection {
moduleName: string;
entries: DocEntry[];
}
import type {DocEntry, EntryCollection, JsDocTagEntry} from '@angular/compiler-cli';
export interface ManifestEntry {
name: string;
@@ -16,7 +10,12 @@ export interface ManifestEntry {
}
/** Manifest that maps each module name to a list of API symbols. */
export type Manifest = Record<string, ManifestEntry[]>;
export type Manifest = {
moduleName: string;
normalizedModuleName: string;
moduleLabel: string;
entries: ManifestEntry[];
}[];
/** Gets a unique lookup key for an API, e.g. "@angular/core/ElementRef". */
function getApiLookupKey(moduleName: string, name: string) {
@@ -114,22 +113,39 @@ export function generateManifest(apiCollections: EntryCollection[]): Manifest {
});
}
const manifest: Manifest = {};
const manifest: Manifest = [];
for (const collection of apiCollections) {
if (!manifest[collection.moduleName]) {
manifest[collection.moduleName] = [];
const entries = collection.entries.map((entry) => ({
name: entry.name,
type: entry.entryType,
isDeprecated: isDeprecated(entryLookup, collection.moduleName, entry),
isDeveloperPreview: isDeveloperPreview(entryLookup, collection.moduleName, entry),
isExperimental: isExperimental(entryLookup, collection.moduleName, entry),
}));
const existingEntry = manifest.find((entry) => entry.moduleName === collection.moduleName);
if (existingEntry) {
existingEntry.entries.push(...entries);
} else {
manifest.push({
moduleName: collection.moduleName,
normalizedModuleName: collection.normalizedModuleName,
moduleLabel: collection.moduleLabel ?? collection.moduleName,
entries,
});
}
}
manifest.sort((entry1, entry2) => {
// Ensure that labels that start with a `code` tag like `window.ng` are last
if (entry1.moduleLabel.startsWith('<')) {
return 1;
} else if (entry2.moduleLabel.startsWith('<')) {
return -1;
}
manifest[collection.moduleName].push(
...collection.entries.map((entry) => ({
name: entry.name,
type: entry.entryType,
isDeprecated: isDeprecated(entryLookup, collection.moduleName, entry),
isDeveloperPreview: isDeveloperPreview(entryLookup, collection.moduleName, entry),
isExperimental: isExperimental(entryLookup, collection.moduleName, entry),
})),
);
}
return entry1.moduleLabel.localeCompare(entry2.moduleLabel);
});
return manifest;
}
@@ -1,5 +1,6 @@
import {readFileSync, writeFileSync} from 'fs';
import {EntryCollection, generateManifest} from './generate_manifest';
import {generateManifest} from './generate_manifest';
import type {EntryCollection} from '@angular/compiler-cli';
function main() {
const [paramFilePath] = process.argv.slice(2);
@@ -16,6 +16,7 @@ extract_api_to_json(
entry_point = "another-fake-source.ts",
module_name = "@angular/router",
output_name = "api.json",
private_modules = [""],
)
ts_library(
@@ -1,40 +1,54 @@
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
import {DocEntry, EntryType, JsDocTagEntry} from '@angular/compiler-cli';
import {generateManifest} from '../generate_manifest';
import {generateManifest, Manifest} from '../generate_manifest';
describe('api manifest generation', () => {
it('should generate a manifest from multiple collections', () => {
const manifest = generateManifest([
const manifest: Manifest = generateManifest([
{
moduleName: '@angular/core',
entries: [entry({name: 'PI', entryType: EntryType.Constant})],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
{
moduleName: '@angular/router',
entries: [entry({name: 'Router', entryType: EntryType.UndecoratedClass})],
normalizedModuleName: 'angular_router',
moduleLabel: 'router',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
'@angular/router': [
{
name: 'Router',
type: EntryType.UndecoratedClass,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
moduleLabel: 'core',
normalizedModuleName: 'angular_core',
entries: [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
{
moduleName: '@angular/router',
moduleLabel: 'router',
normalizedModuleName: 'angular_router',
entries: [
{
name: 'Router',
type: EntryType.UndecoratedClass,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
]);
});
it('should generate a manifest when collections share a symbol with the same name', () => {
@@ -42,33 +56,47 @@ describe('api manifest generation', () => {
{
moduleName: '@angular/core',
entries: [entry({name: 'PI', entryType: EntryType.Constant})],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
{
moduleName: '@angular/router',
entries: [entry({name: 'PI', entryType: EntryType.Constant})],
normalizedModuleName: 'angular_router',
moduleLabel: 'router',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
'@angular/router': [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
moduleLabel: 'core',
normalizedModuleName: 'angular_core',
entries: [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
{
moduleName: '@angular/router',
moduleLabel: 'router',
normalizedModuleName: 'angular_router',
entries: [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
]);
});
it('should union collections for the same module into one manifest', () => {
@@ -76,31 +104,40 @@ describe('api manifest generation', () => {
{
moduleName: '@angular/core',
entries: [entry({name: 'PI', entryType: EntryType.Constant})],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
{
moduleName: '@angular/core',
entries: [entry({name: 'TAO', entryType: EntryType.Constant})],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
{
name: 'TAO',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
moduleLabel: 'core',
normalizedModuleName: 'angular_core',
entries: [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
{
name: 'TAO',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
]);
});
it('should mark a manifest entry as deprecated', () => {
@@ -111,27 +148,34 @@ describe('api manifest generation', () => {
entry({name: 'PI', entryType: EntryType.Constant, jsdocTags: jsdocTags('deprecated')}),
entry({name: 'XI', entryType: EntryType.Constant, jsdocTags: jsdocTags('experimental')}),
],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: true,
isDeveloperPreview: false,
isExperimental: false,
},
{
name: 'XI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: true,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
moduleLabel: 'core',
normalizedModuleName: 'angular_core',
entries: [
{
name: 'PI',
type: EntryType.Constant,
isDeprecated: true,
isDeveloperPreview: false,
isExperimental: false,
},
{
name: 'XI',
type: EntryType.Constant,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: true,
},
],
},
]);
});
it('should deduplicate function overloads', () => {
@@ -142,20 +186,27 @@ describe('api manifest generation', () => {
entry({name: 'save', entryType: EntryType.Function}),
entry({name: 'save', entryType: EntryType.Function}),
],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'save',
type: EntryType.Function,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
moduleLabel: 'core',
normalizedModuleName: 'angular_core',
entries: [
{
name: 'save',
type: EntryType.Function,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
]);
});
it('should not mark a function as deprecated if only one overload is deprecated', () => {
@@ -166,20 +217,27 @@ describe('api manifest generation', () => {
entry({name: 'save', entryType: EntryType.Function}),
entry({name: 'save', entryType: EntryType.Function, jsdocTags: jsdocTags('deprecated')}),
],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'save',
type: EntryType.Function,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
moduleLabel: 'core',
normalizedModuleName: 'angular_core',
entries: [
{
name: 'save',
type: EntryType.Function,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
]);
});
it('should mark a function as deprecated if all overloads are deprecated', () => {
@@ -190,20 +248,27 @@ describe('api manifest generation', () => {
entry({name: 'save', entryType: EntryType.Function, jsdocTags: jsdocTags('deprecated')}),
entry({name: 'save', entryType: EntryType.Function, jsdocTags: jsdocTags('deprecated')}),
],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'save',
type: EntryType.Function,
isDeprecated: true,
isDeveloperPreview: false,
isExperimental: false,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
entries: [
{
name: 'save',
type: EntryType.Function,
isDeprecated: true,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
]);
});
it("should mark a fn as deprecated if there's one w/ the same name in another collection", () => {
@@ -213,33 +278,47 @@ describe('api manifest generation', () => {
entries: [
entry({name: 'save', entryType: EntryType.Function, jsdocTags: jsdocTags('deprecated')}),
],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
{
moduleName: '@angular/more',
entries: [entry({name: 'save', entryType: EntryType.Function})],
normalizedModuleName: 'angular_more',
moduleLabel: 'more',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'save',
type: EntryType.Function,
isDeprecated: true,
isDeveloperPreview: false,
isExperimental: false,
},
],
'@angular/more': [
{
name: 'save',
type: EntryType.Function,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
moduleLabel: 'core',
normalizedModuleName: 'angular_core',
entries: [
{
name: 'save',
type: EntryType.Function,
isDeprecated: true,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
{
moduleName: '@angular/more',
moduleLabel: 'more',
normalizedModuleName: 'angular_more',
entries: [
{
name: 'save',
type: EntryType.Function,
isDeprecated: false,
isDeveloperPreview: false,
isExperimental: false,
},
],
},
]);
});
it('should mark a manifest entry as developerPreview', () => {
@@ -254,27 +333,34 @@ describe('api manifest generation', () => {
}),
entry({name: 'XI', entryType: EntryType.Constant, jsdocTags: jsdocTags('experimental')}),
],
normalizedModuleName: 'angular_core',
moduleLabel: 'core',
},
]);
expect(manifest).toEqual({
'@angular/core': [
{
name: 'PI',
type: EntryType.Constant,
isDeveloperPreview: true,
isDeprecated: false,
isExperimental: false,
},
{
name: 'XI',
type: EntryType.Constant,
isDeveloperPreview: false,
isDeprecated: false,
isExperimental: true,
},
],
});
expect(manifest).toEqual([
{
moduleName: '@angular/core',
moduleLabel: 'core',
normalizedModuleName: 'angular_core',
entries: [
{
name: 'PI',
type: EntryType.Constant,
isDeveloperPreview: true,
isDeprecated: false,
isExperimental: false,
},
{
name: 'XI',
type: EntryType.Constant,
isDeveloperPreview: false,
isDeprecated: false,
isExperimental: true,
},
],
},
]);
});
});
@@ -3,7 +3,7 @@
* 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
* found in the LICENSE file at https://angular.dev/license
*/
// This file is temporarily copied from work-in-progress on
@@ -101,6 +101,8 @@ export interface ClassEntry extends DocEntry {
isAbstract: boolean;
members: MemberEntry[];
generics: GenericEntry[];
extends?: string;
implements: string[];
}
// From an API doc perspective, class and interfaces are identical.
@@ -132,15 +134,25 @@ export interface PipeEntry extends ClassEntry {
// TODO: add `isPure`.
}
export interface FunctionEntry extends DocEntry {
export interface FunctionSignatureMetadata extends DocEntry {
params: ParameterEntry[];
returnType: string;
returnDescription?: string;
generics: GenericEntry[];
isNewType: boolean;
}
export interface FunctionWithOverloadsEntry extends FunctionEntry {
overloads: FunctionEntry[] | null;
export type FunctionEntry = FunctionDefinitionEntry &
DocEntry & {
implementation: FunctionSignatureMetadata;
};
/** Interface describing a function with overload signatures. */
export interface FunctionDefinitionEntry {
name: string;
signatures: FunctionSignatureMetadata[];
implementation: FunctionSignatureMetadata | null;
}
/** Sub-entry for a single class or enum member. */
@@ -178,15 +190,9 @@ export interface ParameterEntry {
isRestParam: boolean;
}
export interface FunctionWithOverloads {
name: string;
signatures: FunctionEntry[];
implementation: FunctionEntry | null;
}
export interface InitializerApiFunctionEntry extends DocEntry {
callFunction: FunctionWithOverloads;
subFunctions: FunctionWithOverloads[];
callFunction: FunctionDefinitionEntry;
subFunctions: FunctionDefinitionEntry[];
__docsMetadata__?: {
/**
@@ -204,7 +210,3 @@ export interface InitializerApiFunctionEntry extends DocEntry {
export function isDocEntryWithSourceInfo(entry: DocEntry): entry is DocEntryWithSourceInfo {
return 'source' in entry;
}
export function isFunctionEntryWithOverloads(entry: DocEntry): entry is FunctionWithOverloadsEntry {
return 'overloads' in entry;
}
@@ -1,6 +1,7 @@
import {
ClassEntry,
ConstantEntry,
DecoratorEntry,
DocEntry,
EntryType,
EnumEntry,
@@ -20,6 +21,7 @@ import {
ClassEntryRenderable,
CliCommandRenderable,
ConstantEntryRenderable,
DecoratorEntryRenderable,
DocEntryRenderable,
EnumEntryRenderable,
FunctionEntryRenderable,
@@ -42,11 +44,16 @@ export function isClassEntry(entry: DocEntry): entry is ClassEntry {
entry.entryType === EntryType.Component ||
entry.entryType === EntryType.Pipe ||
entry.entryType === EntryType.NgModule ||
entry.entryType === EntryType.Directive ||
entry.entryType === EntryType.Decorator
entry.entryType === EntryType.Directive
);
}
export function isDecoratorEntry(entry: DocEntryRenderable): entry is DecoratorEntryRenderable;
export function isDecoratorEntry(entry: DocEntry): entry is DecoratorEntry;
export function isDecoratorEntry(entry: DocEntry): entry is DecoratorEntry {
return entry.entryType === EntryType.Decorator;
}
/** Gets whether the given entry represents a constant */
export function isConstantEntry(entry: DocEntryRenderable): entry is ConstantEntryRenderable;
export function isConstantEntry(entry: DocEntry): entry is ConstantEntry;
@@ -9,9 +9,11 @@
import {
ClassEntry,
ConstantEntry,
DecoratorEntry,
DocEntry,
EnumEntry,
FunctionEntry,
FunctionSignatureMetadata,
InitializerApiFunctionEntry,
JsDocTagEntry,
MemberEntry,
@@ -55,7 +57,13 @@ export type TypeAliasEntryRenderable = TypeAliasEntry & DocEntryRenderable & Has
export type ClassEntryRenderable = ClassEntry &
DocEntryRenderable &
HasRenderableToc & {
membersGroups: Map<string, MemberEntryRenderable[]>;
members: MemberEntryRenderable[];
};
export type DecoratorEntryRenderable = DecoratorEntry &
DocEntryRenderable &
HasRenderableToc & {
members: MemberEntryRenderable[];
};
/** Documentation entity for a TypeScript enum augmented transformed content for rendering. */
@@ -71,9 +79,12 @@ export type InterfaceEntryRenderable = ClassEntryRenderable;
export type FunctionEntryRenderable = FunctionEntry &
DocEntryRenderable &
HasRenderableToc & {
params: ParameterEntryRenderable[];
deprecationMessage: string | null;
overloads: FunctionEntryRenderable[] | null;
};
export type FunctionSignatureMetadataRenderable = FunctionSignatureMetadata &
DocEntryRenderable & {
params: ParameterEntryRenderable[];
};
/** Sub-entry for a single class or enum member augmented with transformed content for rendering. */
@@ -1,8 +1,7 @@
import {FunctionEntry, JsDocTagEntry, MemberEntry, ParameterEntry} from '../entities';
import {JsDocTagEntry, MemberEntry, ParameterEntry} from '../entities';
import {
CodeLineRenderable,
FunctionEntryRenderable,
JsDocTagRenderable,
LinkEntryRenderable,
MemberEntryRenderable,
@@ -81,14 +80,6 @@ export interface HasRenderableParams {
params: ParameterEntryRenderable[];
}
export interface HasOverloads {
overloads: FunctionEntry[] | null;
}
export interface HasRenderableOverloads {
overloads: FunctionEntryRenderable[] | null;
}
export interface HasDeprecatedFlag {
isDeprecated: boolean;
deprecationMessage: string | null;
@@ -7,20 +7,27 @@ import {configureMarkedGlobally} from './marked/configuration';
import {getRenderable} from './processing';
import {renderEntry} from './rendering';
import {initHighlighter} from './shiki/shiki';
import {setCurrentSymbol, setSymbols} from './symbol-context';
/** The JSON data file format for extracted API reference info. */
interface EntryCollection {
moduleName: string;
moduleLabel?: string;
normalizedModuleName: string;
entries: DocEntry[];
symbols: Map<string, string>;
}
/** Parse all JSON data source files into an array of collections. */
function parseEntryData(srcs: string[]): EntryCollection[] {
return srcs.flatMap((jsonDataFilePath) => {
return srcs.flatMap((jsonDataFilePath): EntryCollection | EntryCollection[] => {
const fileContent = readFileSync(jsonDataFilePath, {encoding: 'utf8'});
const fileContentJson = JSON.parse(fileContent) as unknown;
if ((fileContentJson as EntryCollection).entries) {
return fileContentJson as EntryCollection;
return {
...(fileContentJson as EntryCollection),
symbols: new Map((fileContentJson as any).symbols ?? []),
};
}
// CLI subcommands should generate a separate file for each subcommand.
@@ -30,12 +37,16 @@ function parseEntryData(srcs: string[]): EntryCollection[] {
return [
{
moduleName: 'unknown',
normalizedModuleName: 'unknown',
entries: [fileContentJson as DocEntry],
symbols: new Map(),
},
...command.subcommands!.map((subCommand) => {
return {
moduleName: 'unknown',
normalizedModuleName: 'unknown',
entries: [{...subCommand, parentCommand: command} as any],
symbols: new Map(),
};
}),
];
@@ -43,13 +54,15 @@ function parseEntryData(srcs: string[]): EntryCollection[] {
return {
moduleName: 'unknown',
normalizedModuleName: 'unknown',
entries: [fileContentJson as DocEntry], // TODO: fix the typing cli entries aren't DocEntry
symbols: new Map(),
};
});
}
/** Gets a normalized filename for a doc entry. */
function getNormalizedFilename(moduleName: string, entry: DocEntry | CliCommand): string {
function getNormalizedFilename(normalizedModuleName: string, entry: DocEntry | CliCommand): string {
if (isCliEntry(entry)) {
return entry.parentCommand
? `${entry.parentCommand.name}/${entry.name}.html`
@@ -57,9 +70,6 @@ function getNormalizedFilename(moduleName: string, entry: DocEntry | CliCommand)
}
entry = entry as DocEntry;
// Angular entry points all contain an "@" character, which we want to remove
// from the filename. We also swap `/` with an underscore.
const normalizedModuleName = moduleName.replace('@', '').replace(/\//g, '_');
// Append entry type as suffix to prevent writing to file that only differs in casing or query string from already written file.
// This will lead to a race-condition and corrupted files on case-insensitive file systems.
@@ -96,14 +106,22 @@ async function main() {
for (const collection of entryCollections) {
const extractedEntries = collection.entries;
const renderableEntries = extractedEntries.map((entry) =>
getRenderable(entry, collection.moduleName),
);
// Setting the symbols are a global context for the rendering templates of this entry
setSymbols(collection.symbols);
const renderableEntries = extractedEntries.map((entry) => {
setCurrentSymbol(entry.name);
return getRenderable(entry, collection.moduleName);
});
const htmlOutputs = renderableEntries.map(renderEntry);
for (let i = 0; i < htmlOutputs.length; i++) {
const filename = getNormalizedFilename(collection.moduleName, collection.entries[i]);
const filename = getNormalizedFilename(
collection.normalizedModuleName,
collection.entries[i],
);
const outputPath = path.join(outputFilenameExecRootRelativePath, filename);
// in case the output path is nested, ensure the directory exists
@@ -6,16 +6,18 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Renderer as MarkedRenderer} from 'marked';
import {Renderer, Tokens} from 'marked';
import {codeToHtml} from '../shiki/shiki';
/**
* Custom renderer for marked that will be used to transform markdown files to HTML
* files that can be used in the Angular docs.
*/
export const renderer: Partial<MarkedRenderer> = {
code(code: string, language: string, isEscaped: boolean): string {
const highlightResult = codeToHtml(code, language).replace(/>\s+</g, '><');
export const renderer: Partial<Renderer> = {
code({lang, text}): string {
const highlightResult = codeToHtml(text, lang)
// remove spaces/line-breaks between elements to not mess-up `pre` style
.replace(/>\s+</g, '><');
return `
<div class="docs-code" role="group">
@@ -25,37 +27,46 @@ export const renderer: Partial<MarkedRenderer> = {
</div>
`;
},
image(href: string | null, title: string | null, text: string): string {
image({href, title, text}): string {
return `
<img src="${href}" alt="${text}" title="${title}" class="docs-image">
`;
},
link(href: string, title: string, text: string): string {
return `<a href="${href}">${text}</a>`;
link(this: Renderer, {href, tokens}): string {
return `<a href="${href}">${this.parser.parseInline(tokens)}</a>`;
},
list(body: string, ordered: boolean, start: number) {
list(this: Renderer, {items, ordered, start}) {
if (ordered) {
return `
<ol class="docs-ordered-list">
${body}
${items.map((item) => this.listitem(item)).join('')}
</ol>
`;
}
return `
<ul class="docs-list">
${body}
${items.map((item) => this.listitem(item)).join('')}
</ul>
`;
},
table(header: string, body: string): string {
table(this: Renderer, {header, rows}: Tokens.Table) {
return `
<div class="docs-table docs-scroll-track-transparent">
<table>
<thead>
${header}
${this.tablerow({
text: header.map((cell) => this.tablecell(cell)).join(''),
})}
</thead>
<tbody>
${body}
${rows
.map((row) =>
this.tablerow({
text: row.map((cell) => this.tablecell(cell)).join(''),
}),
)
.join('')}
</tbody>
</table>
</div>
@@ -12,6 +12,7 @@ import {
isClassEntry,
isCliEntry,
isConstantEntry,
isDecoratorEntry,
isEnumEntry,
isFunctionEntry,
isInitializerApiFunctionEntry,
@@ -20,6 +21,7 @@ import {
} from './entities/categorization';
import {CliCommandRenderable, DocEntryRenderable} from './entities/renderables';
import {getClassRenderable} from './transforms/class-transforms';
import {getDecoratorRenderable} from './transforms/decorator-transforms';
import {getCliRenderable} from './transforms/cli-transforms';
import {getConstantRenderable} from './transforms/constant-transforms';
import {getEnumRenderable} from './transforms/enum-transforms';
@@ -43,6 +45,9 @@ export function getRenderable(
if (isClassEntry(entry)) {
return getClassRenderable(entry, moduleName);
}
if (isDecoratorEntry(entry)) {
return getDecoratorRenderable(entry, moduleName);
}
if (isConstantEntry(entry)) {
return getConstantRenderable(entry, moduleName);
}
@@ -12,6 +12,7 @@ import {
isClassEntry,
isCliEntry,
isConstantEntry,
isDecoratorEntry,
isEnumEntry,
isFunctionEntry,
isInitializerApiFunctionEntry,
@@ -27,14 +28,16 @@ import {EnumReference} from './templates/enum-reference';
import {FunctionReference} from './templates/function-reference';
import {InitializerApiFunction} from './templates/initializer-api-function';
import {TypeAliasReference} from './templates/type-alias-reference';
import {setCurrentSymbol} from './symbol-context';
/** Given a doc entry, get the transformed version of the entry for rendering. */
export function renderEntry(renderable: DocEntryRenderable | CliCommandRenderable): string {
setCurrentSymbol(renderable.name);
if (isCliEntry(renderable)) {
return render(CliCommandReference(renderable));
}
if (isClassEntry(renderable) || isInterfaceEntry(renderable)) {
if (isClassEntry(renderable) || isInterfaceEntry(renderable) || isDecoratorEntry(renderable)) {
return render(ClassReference(renderable));
}
if (isConstantEntry(renderable)) {
@@ -21,8 +21,12 @@ export async function initHighlighter() {
});
}
export function codeToHtml(code: string, language: string | undefined): string {
return highlighter.codeToHtml(code, {
export function codeToHtml(
code: string,
language: string | undefined,
options?: {removeFunctionKeyword?: boolean},
): string {
const html = highlighter.codeToHtml(code, {
lang: language ?? 'text',
themes: {
light: 'github-light',
@@ -31,4 +35,24 @@ export function codeToHtml(code: string, language: string | undefined): string {
cssVariablePrefix: '--shiki-',
defaultColor: false,
});
if (options?.removeFunctionKeyword) {
return replaceKeywordFromShikiHtml('function', html);
}
return html;
}
export function replaceKeywordFromShikiHtml(
keyword: string,
shikiHtml: string,
replaceWith = '',
): string {
return (
shikiHtml
// remove the leading space of the element after the "function" element
.replace(new RegExp(`(<[^>]*>${keyword}<\\/\\w+><[^>]*>)(\\s)(\\w+<\\/\\w+>)`, 'g'), '$1$3')
// Shiki requires the keyword function for highlighting functions signatures
// We don't want to display it so we remove elements with the keyword
.replace(new RegExp(`<[^>]*>${keyword}<\\/\\w+>`, 'g'), replaceWith)
);
}
@@ -3,7 +3,7 @@
* 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
* found in the LICENSE file at https://angular.dev/license
*/
// TODO(jelbourn): all of these CSS classes should use the `docs-` prefix.
@@ -16,6 +16,7 @@ export const REFERENCE_MEMBERS = 'docs-reference-members';
export const REFERENCE_DEPRECATED = 'docs-reference-deprecated';
export const REFERENCE_MEMBERS_CONTAINER = 'docs-reference-members-container';
export const REFERENCE_MEMBER_CARD = 'docs-reference-member-card';
export const REFERENCE_MEMBER_CARD_HEADER = 'docs-reference-card-header';
export const REFERENCE_MEMBER_CARD_BODY = 'docs-reference-card-body';
export const REFERENCE_MEMBER_CARD_ITEM = 'docs-reference-card-item';
@@ -0,0 +1,37 @@
/**
* API pages are generated each package at a time.
* This allows to use a global context to store the symbols and their corresponding module names.
*/
let symbols = new Map<string, string>();
// This is used to store the currently processed symbol (usually a class or an interface)
let currentSymbol: string | undefined;
export function setSymbols(newSymbols: Map<string, string>): void {
symbols = newSymbols;
}
/**
* Returns the module name of a symbol.
* eg: 'ApplicationRef' => 'core', 'FormControl' => 'forms'
* Also supports class.member, 'NgZone.runOutsideAngular => 'core'
*/
export function getModuleName(symbol: string): string | undefined {
const moduleName = symbols.get(symbol);
return moduleName?.replace('@angular/', '');
}
export function setCurrentSymbol(symbol: string): void {
currentSymbol = symbol;
}
export function getCurrentSymbol(): string | undefined {
return currentSymbol;
}
export function logUnknownSymbol(link: string, symbol: string): void {
console.warn(
`WARNING: {@link ${link}} is invalid, ${symbol} or ${currentSymbol}.${symbol} is unknown in this context`,
);
}
@@ -10,11 +10,11 @@ import {h} from 'preact';
import {MemberEntryRenderable} from '../entities/renderables';
import {ClassMember} from './class-member';
export function ClassMemberList(props: {membersGroups: Map<string, MemberEntryRenderable[]>}) {
export function ClassMemberList(props: {members: MemberEntryRenderable[]}) {
return (
<div class="docs-reference-members">
{Array.from(props.membersGroups).map(([_, group]) => (
<ClassMember members={group} />
{props.members.map((member) => (
<ClassMember member={member} />
))}
</div>
);
@@ -15,53 +15,51 @@ import {
} from '../entities/categorization';
import {MemberEntryRenderable} from '../entities/renderables';
import {
REFERENCE_HEADER,
REFERENCE_MEMBER_CARD,
REFERENCE_MEMBER_CARD_BODY,
REFERENCE_MEMBER_CARD_HEADER,
REFERENCE_MEMBER_CARD_ITEM,
} from '../styling/css-classes';
import {ClassMethodInfo} from './class-method-info';
import {DeprecatedLabel} from './deprecated-label';
import {RawHtml} from './raw-html';
import {getFunctionMetadataRenderable} from '../transforms/function-transforms';
import {CodeSymbol} from './code-symbols';
export function ClassMember(props: {members: MemberEntryRenderable[]}) {
const memberName = props.members[0].name;
const returnType = getMemberType(props.members[0]);
// Do not create body element when there is no description
const body = props.members.every(
(member) => !member.htmlDescription && !isClassMethodEntry(member),
) ? (
<></>
) : (
export function ClassMember(props: {member: MemberEntryRenderable}) {
const body = (
<div className={REFERENCE_MEMBER_CARD_BODY}>
{props.members.map((member) => {
return isClassMethodEntry(member) ? (
<ClassMethodInfo entry={member} isOverloaded={props.members.length > 1} />
) : (
<div className={REFERENCE_MEMBER_CARD_ITEM}>
{props.members.every((member) => member.deprecationMessage !== null) ? (
<DeprecatedLabel entry={props.members[0]} />
) : (
<></>
)}
<RawHtml value={member.htmlDescription} />
</div>
);
})}
{isClassMethodEntry(props.member) ? (
(props.member.signatures.length
? props.member.signatures
: [props.member.implementation]
).map((sig) => {
const renderableMember = getFunctionMetadataRenderable(sig);
return <ClassMethodInfo entry={renderableMember} options={{showUsageNotes: true}} />;
})
) : props.member.htmlDescription || props.member.deprecationMessage ? (
<div className={REFERENCE_MEMBER_CARD_ITEM}>
<DeprecatedLabel entry={props.member} />
<RawHtml value={props.member.htmlDescription} />
</div>
) : (
<></>
)}
</div>
);
const memberName = props.member.name;
const returnType = getMemberType(props.member);
return (
<div id={memberName} className={REFERENCE_MEMBER_CARD} tabIndex={-1}>
<header>
<div className={REFERENCE_HEADER}>
<div className={REFERENCE_MEMBER_CARD_HEADER}>
<h3>{memberName}</h3>
<div>
{props.members.length > 1 ? (
<span>{props.members.length} overloads</span>
{isClassMethodEntry(props.member) && props.member.signatures.length > 1 ? (
<span>{props.member.signatures.length} overloads</span>
) : returnType ? (
<code>{returnType}</code>
<CodeSymbol code={returnType} />
) : (
<></>
)}
@@ -75,7 +73,7 @@ export function ClassMember(props: {members: MemberEntryRenderable[]}) {
function getMemberType(entry: MemberEntryRenderable): string | null {
if (isClassMethodEntry(entry)) {
return entry.returnType;
return entry.implementation.returnType;
} else if (isPropertyEntry(entry) || isGetterEntry(entry) || isSetterEntry(entry)) {
return entry.type;
}
@@ -9,6 +9,7 @@
import {Fragment, h} from 'preact';
import {
FunctionEntryRenderable,
FunctionSignatureMetadataRenderable,
MethodEntryRenderable,
ParameterEntryRenderable,
} from '../entities/renderables';
@@ -16,13 +17,16 @@ import {PARAM_KEYWORD_CLASS_NAME, REFERENCE_MEMBER_CARD_ITEM} from '../styling/c
import {DeprecatedLabel} from './deprecated-label';
import {Parameter} from './parameter';
import {RawHtml} from './raw-html';
import {CodeSymbol} from './code-symbols';
/**
* Component to render the method-specific parts of a class's API reference.
*/
export function ClassMethodInfo(props: {
entry: MethodEntryRenderable | FunctionEntryRenderable;
isOverloaded?: boolean;
entry: FunctionSignatureMetadataRenderable;
options?: {
showUsageNotes?: boolean;
};
}) {
const entry = props.entry;
@@ -32,21 +36,21 @@ export function ClassMethodInfo(props: {
>
<RawHtml value={entry.htmlDescription} className={'docs-function-definition'} />
{/* In case when method is overloaded we need to indicate which overload is deprecated */}
{!props.isOverloaded ? (
<></>
) : (
{entry.isDeprecated ? (
<div>
<DeprecatedLabel entry={entry} />
</div>
) : (
<></>
)}
{entry.params.map((param: ParameterEntryRenderable) => (
<Parameter param={param} />
))}
<div className={'docs-return-type'}>
<span className={PARAM_KEYWORD_CLASS_NAME}>@returns</span>
<code>{entry.returnType}</code>
<CodeSymbol code={entry.returnType} />
</div>
{entry.htmlUsageNotes ? (
{entry.htmlUsageNotes && props.options?.showUsageNotes ? (
<div className={'docs-usage-notes'}>
<span className={PARAM_KEYWORD_CLASS_NAME}>Usage notes</span>
<RawHtml value={entry.htmlUsageNotes} />
@@ -7,7 +7,7 @@
*/
import {Fragment, h} from 'preact';
import {ClassEntryRenderable} from '../entities/renderables';
import {ClassEntryRenderable, DecoratorEntryRenderable} from '../entities/renderables';
import {ClassMemberList} from './class-member-list';
import {HeaderApi} from './header-api';
import {REFERENCE_MEMBERS_CONTAINER} from '../styling/css-classes';
@@ -16,20 +16,20 @@ import {TabUsageNotes} from './tab-usage-notes';
import {TabApi} from './tab-api';
/** Component to render a class API reference document. */
export function ClassReference(entry: ClassEntryRenderable) {
export function ClassReference(entry: ClassEntryRenderable | DecoratorEntryRenderable) {
return (
<div class="api">
<HeaderApi entry={entry} />
<TabApi entry={entry} />
<TabDescription entry={entry} />
<TabUsageNotes entry={entry} />
{
entry.membersGroups.size > 0
? (<div class={REFERENCE_MEMBERS_CONTAINER}>
<ClassMemberList membersGroups={entry.membersGroups} />
</div>)
: (<></>)
}
{entry.members.length > 0 ? (
<div class={REFERENCE_MEMBERS_CONTAINER}>
<ClassMemberList members={entry.members} />
</div>
) : (
<></>
)}
</div>
);
}
@@ -9,12 +9,13 @@
import {Fragment, h} from 'preact';
import {CliCardRenderable} from '../entities/renderables';
import {DeprecatedLabel} from './deprecated-label';
import { REFERENCE_MEMBER_CARD, REFERENCE_MEMBER_CARD_HEADER } from '../styling/css-classes';
export function CliCard(props: {card: CliCardRenderable}) {
return (
<div id={props.card.type} class="docs-reference-member-card" tabIndex={-1}>
<div id={props.card.type} class={REFERENCE_MEMBER_CARD} tabIndex={-1}>
<header>
<div class="docs-card-ref-header">
<div class={REFERENCE_MEMBER_CARD_HEADER}>
<h3>{props.card.type}</h3>
</div>
</header>
@@ -23,7 +23,7 @@ export function CliCommandReference(entry: CliCommandRenderable) {
<div class="docs-code docs-reference-cli-toc">
<pre class="docs-mini-scroll-track">
<code>
<div className={'shiki-ln-line'}>
<div className={'shiki line cli'}>
ng {commandName(entry, command)}
{entry.argumentsLabel ? <button member-id={'Arguments'} className="shiki-ln-line-argument">{entry.argumentsLabel}</button> : <></>}
{entry.hasOptions ? <button member-id={'Options'} className="shiki-ln-line-option">[options]</button> : <></>}
@@ -0,0 +1,30 @@
import {h} from 'preact';
import {getModuleName} from '../symbol-context';
import {getLinkToModule} from '../transforms/url-transforms';
const symbolRegex = /([a-zA-Z_$][a-zA-Z_$0-9\.]*)/;
/**
* Component that generates a code block with a link to a Symbol if it's known,
* else generates a string code block
*/
export function CodeSymbol(props: {code: string}) {
return (
<code>
{props.code.split(symbolRegex).map((rawSymbol, index) => {
// Every even index is a non-match when the regex has 1 capturing group
if (index % 2 === 0) return rawSymbol;
let [symbol, subSymbol] = rawSymbol.split('.'); // Also takes care of methods, enum value etc.
const moduleName = getModuleName(symbol);
if (moduleName) {
const url = getLinkToModule(moduleName, symbol, subSymbol);
return <a href={url}>{rawSymbol}</a>;
}
return rawSymbol;
})}
</code>
);
}
@@ -3,7 +3,7 @@
* 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
* found in the LICENSE file at https://angular.dev/license
*/
import {Fragment, h} from 'preact';
@@ -7,7 +7,7 @@
*/
import {h, Fragment} from 'preact';
import {EnumEntryRenderable} from '../entities/renderables';
import {EnumEntryRenderable, MemberEntryRenderable} from '../entities/renderables';
import {HeaderApi} from './header-api';
import {TabDescription} from './tab-description';
import {TabApi} from './tab-api';
@@ -26,7 +26,7 @@ export function EnumReference(entry: EnumEntryRenderable) {
? (
<div class={REFERENCE_MEMBERS_CONTAINER}>
<div class={REFERENCE_MEMBERS}>
{entry.members.map((member: any) => (<ClassMember members={[member]}/>))}
{entry.members.map((member: MemberEntryRenderable) => (<ClassMember member={member}/>))}
</div>
</div>
)
@@ -7,23 +7,66 @@
*/
import {h} from 'preact';
import {FunctionEntryRenderable} from '../entities/renderables';
import {FunctionEntryRenderable, FunctionSignatureMetadataRenderable} from '../entities/renderables';
import {
PARAM_KEYWORD_CLASS_NAME,
REFERENCE_HEADER,
REFERENCE_MEMBERS,
REFERENCE_MEMBERS_CONTAINER,
REFERENCE_MEMBER_CARD,
REFERENCE_MEMBER_CARD_BODY,
REFERENCE_MEMBER_CARD_HEADER,
} from '../styling/css-classes';
import {ClassMethodInfo} from './class-method-info';
import {HeaderApi} from './header-api';
import {TabApi} from './tab-api';
import {TabDescription} from './tab-description';
import {TabUsageNotes} from './tab-usage-notes';
import {HighlightTypeScript} from './highlight-ts';
import {printInitializerFunctionSignatureLine} from '../transforms/code-transforms';
import {getFunctionMetadataRenderable} from '../transforms/function-transforms';
import {CodeSymbol} from './code-symbols';
export const signatureCard = (
name: string,
signature: FunctionSignatureMetadataRenderable,
opts: {id: string},
printSignaturesAsHeader: boolean,
) => {
return (
<div class={REFERENCE_MEMBER_CARD} id={opts.id} tabIndex={-1}>
<header>
{printSignaturesAsHeader ? (
<code>
<HighlightTypeScript
code={printInitializerFunctionSignatureLine(
name,
signature,
// Always omit types in signature headers, to keep them short.
true,
)}
removeFunctionKeyword={true}
/>
</code>
) : (
<div className={REFERENCE_MEMBER_CARD_HEADER}>
<h3>{name}</h3>
<div>
<CodeSymbol code={signature.returnType} />
</div>
</div>
)}
</header>
<div class={REFERENCE_MEMBER_CARD_BODY}>
<ClassMethodInfo entry={signature} />
</div>
</div>
);
};
/** Component to render a function API reference document. */
export function FunctionReference(entry: FunctionEntryRenderable) {
// Use signatures as header if there are multiple signatures.
const printSignaturesAsHeader = entry.signatures.length > 1;
return (
<div class="api">
<HeaderApi entry={entry} />
@@ -32,26 +75,16 @@ export function FunctionReference(entry: FunctionEntryRenderable) {
<TabUsageNotes entry={entry} />
<div className={REFERENCE_MEMBERS_CONTAINER}>
<div className={REFERENCE_MEMBERS}>
<div className={REFERENCE_MEMBER_CARD}>
<header>
<div className={REFERENCE_HEADER}>
<h3>{entry.name}</h3>
<div>
<code>{entry.returnType}</code>
</div>
</div>
{entry.isDeprecated && (
<span className={`${PARAM_KEYWORD_CLASS_NAME} docs-deprecated`}>@deprecated</span>
)}
</header>
<div className={REFERENCE_MEMBER_CARD_BODY}>
{entry.overloads ? (
entry.overloads.map((overload) => <ClassMethodInfo entry={overload} />)
) : (
<ClassMethodInfo entry={entry} isOverloaded={true} />
)}
</div>
</div>
{entry.signatures.map((s, i) =>
signatureCard(
s.name,
getFunctionMetadataRenderable(s, entry.moduleName),
{
id: `${s.name}_${i}`,
},
printSignaturesAsHeader,
),
)}
</div>
</div>
</div>
@@ -3,7 +3,7 @@
* 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
* found in the LICENSE file at https://angular.dev/license
*/
import {h} from 'preact';
@@ -74,12 +74,12 @@ export function HeaderApi(props: {entry: DocEntryRenderable; showFullDescription
)}
</div>
<p
<section
className={'docs-reference-description'}
dangerouslySetInnerHTML={{
__html: props.showFullDescription ? entry.htmlDescription : entry.shortHtmlDescription,
}}
></p>
></section>
<DocsPillRow links={entry.additionalLinks} />
</header>
@@ -3,7 +3,7 @@
* 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
* found in the LICENSE file at https://angular.dev/license
*/
import {h} from 'preact';
@@ -3,18 +3,18 @@
* 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
* found in the LICENSE file at https://angular.dev/license
*/
import { h } from 'preact';
import { RawHtml } from './raw-html';
import { codeToHtml } from '../shiki/shiki';
import {h} from 'preact';
import {RawHtml} from './raw-html';
import {codeToHtml} from '../shiki/shiki';
/** Component to render a header of the CLI page. */
export function HighlightTypeScript(props: {code: string}) {
const result = codeToHtml(props.code, 'typescript');
export function HighlightTypeScript(props: {code: string; removeFunctionKeyword?: boolean}) {
const result = codeToHtml(props.code, 'typescript', {
removeFunctionKeyword: props.removeFunctionKeyword,
});
return (
<RawHtml value={result} />
);
return <RawHtml value={result} />;
}
@@ -7,20 +7,13 @@
*/
import {h, JSX} from 'preact';
import {FunctionEntryRenderable, InitializerApiFunctionRenderable} from '../entities/renderables';
import {InitializerApiFunctionRenderable} from '../entities/renderables';
import {HeaderApi} from './header-api';
import {TabApi} from './tab-api';
import {TabUsageNotes} from './tab-usage-notes';
import {
REFERENCE_MEMBERS,
REFERENCE_MEMBERS_CONTAINER,
REFERENCE_MEMBER_CARD,
REFERENCE_MEMBER_CARD_BODY,
} from '../styling/css-classes';
import {printInitializerFunctionSignatureLine} from '../transforms/code-transforms';
import {HighlightTypeScript} from './highlight-ts';
import {ClassMethodInfo} from './class-method-info';
import {getFunctionRenderable} from '../transforms/function-transforms';
import {REFERENCE_MEMBERS, REFERENCE_MEMBERS_CONTAINER} from '../styling/css-classes';
import {getFunctionMetadataRenderable} from '../transforms/function-transforms';
import {signatureCard} from './function-reference';
/** Component to render a constant API reference document. */
export function InitializerApiFunction(entry: InitializerApiFunctionRenderable) {
@@ -40,32 +33,6 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
entry.callFunction.signatures[0].description = '';
}
const signatureCard = (name: string, signature: FunctionEntryRenderable, opts: {id: string}) => {
return (
<div class={REFERENCE_MEMBER_CARD} id={opts.id} tabIndex={-1}>
<header>
{printSignaturesAsHeader ? (
<code>
<HighlightTypeScript
code={printInitializerFunctionSignatureLine(
name,
signature,
// Always omit types in signature headers, to keep them short.
true,
)}
/>
</code>
) : (
<h3>{`${name}()`}</h3>
)}
</header>
<div class={REFERENCE_MEMBER_CARD_BODY}>
<ClassMethodInfo entry={signature} />
</div>
</div>
);
};
return (
<div class="api">
<HeaderApi entry={entry} showFullDescription={true} />
@@ -75,9 +42,14 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
<div class={REFERENCE_MEMBERS_CONTAINER}>
<div class={REFERENCE_MEMBERS}>
{entry.callFunction.signatures.map((s, i) =>
signatureCard(s.name, getFunctionRenderable(s, entry.moduleName), {
id: `${s.name}_${i}`,
}),
signatureCard(
s.name,
getFunctionMetadataRenderable(s, entry.moduleName),
{
id: `${s.name}_${i}`,
},
printSignaturesAsHeader,
),
)}
{entry.subFunctions.reduce(
@@ -86,10 +58,11 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
...subFunction.signatures.map((s, i) =>
signatureCard(
`${entry.name}.${s.name}`,
getFunctionRenderable(s, entry.moduleName),
getFunctionMetadataRenderable(s, entry.moduleName),
{
id: `${entry.name}_${s.name}_${i}`,
},
printSignaturesAsHeader,
),
),
],
@@ -10,7 +10,7 @@ import {h} from 'preact';
import {ParameterEntryRenderable} from '../entities/renderables';
import {RawHtml} from './raw-html';
import {PARAM_GROUP_CLASS_NAME} from '../styling/css-classes';
import {CodeSymbol} from './code-symbols';
/** Component to render a function or method parameter reference doc fragment. */
export function Parameter(props: {param: ParameterEntryRenderable}) {
@@ -21,7 +21,7 @@ export function Parameter(props: {param: ParameterEntryRenderable}) {
{/*TODO: isOptional, isRestParam*/}
<span class="docs-param-keyword">@param</span>
<span class="docs-param-name">{param.name}</span>
<code>{param.type}</code>
<CodeSymbol code={param.type} />
<RawHtml value={param.htmlDescription} className="docs-parameter-description" />
</div>
);
@@ -10,18 +10,41 @@ import {Fragment, h} from 'preact';
import {DocEntryRenderable} from '../entities/renderables';
import {normalizeTabUrl} from '../transforms/url-transforms';
import {RawHtml} from './raw-html';
import {CodeSymbol} from './code-symbols';
const DESCRIPTION_TAB_NAME = 'Description';
/** Component to render the description tab. */
export function TabDescription(props: {entry: DocEntryRenderable}) {
if (!props.entry.htmlDescription || props.entry.htmlDescription === props.entry.shortHtmlDescription) {
return (<></>);
const exportedBy = props.entry.jsdocTags.filter((t) => t.name === 'ngModule');
if (
(!props.entry.htmlDescription ||
props.entry.htmlDescription === props.entry.shortHtmlDescription) &&
!exportedBy.length
) {
return <></>;
}
return (
<div data-tab={DESCRIPTION_TAB_NAME} data-tab-url={normalizeTabUrl(DESCRIPTION_TAB_NAME)}>
<RawHtml value={props.entry.htmlDescription} />
{exportedBy.length ? (
<>
<hr />
<h2>Exported by</h2>
<ul>
{exportedBy.map((tag) => (
<li>
<CodeSymbol code={tag.comment} />
</li>
))}
</ul>
</>
) : (
<></>
)}
</div>
);
}
@@ -19,10 +19,18 @@ ts_library(
),
deps = [
"//adev/shared-docs/pipeline/api-gen/rendering:render_api_to_html_lib",
"@npm//@bazel/runfiles",
"@npm//@types/jsdom",
"@npm//jsdom",
],
)
jasmine_node_test(
name = "unit_tests",
data = [
"@npm//jsdom",
] + glob([
"**/*.json",
]),
deps = [":unit_test_lib"],
)
@@ -0,0 +1,39 @@
import {runfiles} from '@bazel/runfiles';
import {readFile} from 'fs/promises';
import {JSDOM} from 'jsdom';
import {renderEntry} from '../rendering';
import {getRenderable} from '../processing';
import {initHighlighter} from '../shiki/shiki';
import {configureMarkedGlobally} from '../marked/configuration';
describe('CLI docs to html', () => {
let fragment: DocumentFragment;
let entryJson: any;
beforeAll(async () => {
await initHighlighter();
await configureMarkedGlobally();
const entryContent = await readFile(runfiles.resolvePackageRelative('fake-cli-entries.json'), {
encoding: 'utf-8',
});
entryJson = JSON.parse(entryContent) as any;
const renderableJson = getRenderable(entryJson, '');
fragment = JSDOM.fragment(await renderEntry(renderableJson));
});
it('should subcommands correctly', async () => {
const generateComponentSubcommand = entryJson.subcommands.find(
(subcommand: any) => subcommand.name === 'component',
);
const renderableJson = getRenderable(generateComponentSubcommand, '');
fragment = JSDOM.fragment(await renderEntry(renderableJson));
const cliTocs = fragment.querySelectorAll('.docs-reference-cli-toc')!;
expect(cliTocs.length).toBe(2);
expect(cliTocs[0].textContent).toContain('ng component[name][options]');
expect(cliTocs[1].textContent).toContain('ng c[name][options]');
});
});
@@ -2,7 +2,9 @@
"name": "generate",
"command": "ng generate <schematic>",
"shortDescription": "Generates and/or modifies files based on a schematic.",
"aliases": ["g"],
"aliases": [
"g"
],
"deprecated": false,
"options": [
{
@@ -14,7 +16,9 @@
{
"name": "dry-run",
"type": "boolean",
"aliases": ["d"],
"aliases": [
"d"
],
"default": false,
"description": "Run through and reports activity without writing out results."
},
@@ -62,16 +66,26 @@
"command": "application [name]",
"shortDescription": "Generates a new basic application definition in the \"projects\" subfolder of the workspace.",
"options": [
{
"name": "experimental-zoneless",
"type": "boolean",
"default": false,
"description": "Create an application that does not utilize zone.js."
},
{
"name": "inline-style",
"type": "boolean",
"aliases": ["s"],
"aliases": [
"s"
],
"description": "Include styles inline in the root component.ts file. Only CSS styles can be included inline. Default is false, meaning that an external styles file is created and referenced in the root component.ts file."
},
{
"name": "inline-template",
"type": "boolean",
"aliases": ["t"],
"aliases": [
"t"
],
"description": "Include template inline in the root component.ts file. Default is false, meaning that an external template file is created and referenced in the root component.ts file. "
},
{
@@ -89,7 +103,9 @@
{
"name": "prefix",
"type": "string",
"aliases": ["p"],
"aliases": [
"p"
],
"default": "app",
"description": "A prefix to apply to generated selectors."
},
@@ -119,7 +135,9 @@
{
"name": "skip-tests",
"type": "boolean",
"aliases": ["S"],
"aliases": [
"S"
],
"default": false,
"description": "Do not create \"spec.ts\" test files for the application."
},
@@ -145,17 +163,28 @@
"name": "style",
"type": "string",
"default": "css",
"enum": ["css", "scss", "sass", "less"],
"enum": [
"css",
"scss",
"sass",
"less"
],
"description": "The file extension or preprocessor to use for style files."
},
{
"name": "view-encapsulation",
"type": "string",
"enum": ["Emulated", "None", "ShadowDom"],
"enum": [
"Emulated",
"None",
"ShadowDom"
],
"description": "The view encapsulation strategy to use in the new application."
}
],
"aliases": ["app"],
"aliases": [
"app"
],
"deprecated": false
},
{
@@ -186,7 +215,9 @@
"description": "Adds a developer-defined type to the filename, in the format \"name.type.ts\"."
}
],
"aliases": ["cl"],
"aliases": [
"cl"
],
"deprecated": false
},
{
@@ -197,15 +228,22 @@
{
"name": "change-detection",
"type": "string",
"aliases": ["c"],
"aliases": [
"c"
],
"default": "Default",
"enum": ["Default", "OnPush"],
"enum": [
"Default",
"OnPush"
],
"description": "The change detection strategy to use in the new component."
},
{
"name": "display-block",
"type": "boolean",
"aliases": ["b"],
"aliases": [
"b"
],
"default": false,
"description": "Specifies if the style will contain `:host { display: block; }`."
},
@@ -215,6 +253,12 @@
"default": false,
"description": "The declaring NgModule exports this component."
},
{
"name": "export-default",
"type": "boolean",
"default": false,
"description": "Use default export for the component instead of a named export."
},
{
"name": "flat",
"type": "boolean",
@@ -224,21 +268,27 @@
{
"name": "inline-style",
"type": "boolean",
"aliases": ["s"],
"aliases": [
"s"
],
"default": false,
"description": "Include styles inline in the component.ts file. Only CSS styles can be included inline. By default, an external styles file is created and referenced in the component.ts file."
},
{
"name": "inline-template",
"type": "boolean",
"aliases": ["t"],
"aliases": [
"t"
],
"default": false,
"description": "Include template inline in the component.ts file. By default, an external template file is created and referenced in the component.ts file."
},
{
"name": "module",
"type": "string",
"aliases": ["m"],
"aliases": [
"m"
],
"description": "The declaring NgModule."
},
{
@@ -250,7 +300,9 @@
{
"name": "prefix",
"type": "string",
"aliases": ["p"],
"aliases": [
"p"
],
"description": "The prefix to apply to the generated component selector."
},
{
@@ -291,7 +343,13 @@
"name": "style",
"type": "string",
"default": "css",
"enum": ["css", "scss", "sass", "less", "none"],
"enum": [
"css",
"scss",
"sass",
"less",
"none"
],
"description": "The file extension or preprocessor to use for style files, or 'none' to skip generating the style file."
},
{
@@ -303,12 +361,20 @@
{
"name": "view-encapsulation",
"type": "string",
"aliases": ["v"],
"enum": ["Emulated", "None", "ShadowDom"],
"aliases": [
"v"
],
"enum": [
"Emulated",
"None",
"ShadowDom"
],
"description": "The view encapsulation strategy to use in the new component."
}
],
"aliases": ["c"],
"aliases": [
"c"
],
"deprecated": false
},
{
@@ -324,7 +390,10 @@
{
"name": "type",
"type": "string",
"enum": ["karma", "browserslist"],
"enum": [
"karma",
"browserslist"
],
"description": "Specifies which type of configuration file to create.",
"positional": 0
}
@@ -352,7 +421,9 @@
{
"name": "module",
"type": "string",
"aliases": ["m"],
"aliases": [
"m"
],
"description": "The declaring NgModule."
},
{
@@ -364,7 +435,9 @@
{
"name": "prefix",
"type": "string",
"aliases": ["p"],
"aliases": [
"p"
],
"description": "A prefix to apply to generated selectors."
},
{
@@ -396,7 +469,9 @@
"description": "Whether the generated directive is standalone."
}
],
"aliases": ["d"],
"aliases": [
"d"
],
"deprecated": false
},
{
@@ -421,7 +496,9 @@
"description": "Adds a developer-defined type to the filename, in the format \"name.type.ts\"."
}
],
"aliases": ["e"],
"aliases": [
"e"
],
"deprecated": false
},
{
@@ -458,7 +535,9 @@
{
"name": "implements",
"type": "array",
"aliases": ["guardType"],
"aliases": [
"guardType"
],
"description": "Specifies which type of guard to create."
},
{
@@ -479,7 +558,9 @@
"description": "Do not create \"spec.ts\" test files for the new guard."
}
],
"aliases": ["g"],
"aliases": [
"g"
],
"deprecated": false
},
{
@@ -548,7 +629,9 @@
"positional": 1
}
],
"aliases": ["i"],
"aliases": [
"i"
],
"deprecated": false
},
{
@@ -571,7 +654,9 @@
{
"name": "prefix",
"type": "string",
"aliases": ["p"],
"aliases": [
"p"
],
"default": "lib",
"description": "A prefix to apply to generated selectors."
},
@@ -605,7 +690,9 @@
"description": "Creates a library based upon the standalone API, without NgModules."
}
],
"aliases": ["lib"],
"aliases": [
"lib"
],
"deprecated": false
},
{
@@ -622,7 +709,9 @@
{
"name": "module",
"type": "string",
"aliases": ["m"],
"aliases": [
"m"
],
"description": "The declaring NgModule."
},
{
@@ -651,11 +740,16 @@
"name": "routing-scope",
"type": "string",
"default": "Child",
"enum": ["Child", "Root"],
"enum": [
"Child",
"Root"
],
"description": "The scope for the new routing module."
}
],
"aliases": ["m"],
"aliases": [
"m"
],
"deprecated": false
},
{
@@ -678,7 +772,9 @@
{
"name": "module",
"type": "string",
"aliases": ["m"],
"aliases": [
"m"
],
"description": "The declaring NgModule."
},
{
@@ -711,7 +807,9 @@
"description": "Whether the generated pipe is standalone."
}
],
"aliases": ["p"],
"aliases": [
"p"
],
"deprecated": false
},
{
@@ -749,7 +847,9 @@
"description": "Do not create \"spec.ts\" test files for the new resolver."
}
],
"aliases": ["r"],
"aliases": [
"r"
],
"deprecated": false
},
{
@@ -781,7 +881,9 @@
"description": "Do not create \"spec.ts\" test files for the new service."
}
],
"aliases": ["s"],
"aliases": [
"s"
],
"deprecated": false
},
{
@@ -828,7 +930,7 @@
}
],
"aliases": [],
"deprecated": true
"deprecated": false
}
]
}
}
@@ -3,81 +3,671 @@
"entries": [
{
"name": "NgTemplateOutlet",
"description": "*one* directive",
"isAbstract": false,
"entryType": "directive",
"members": [],
"jsdocTags": [],
"rawComment": "",
"source": {
"fileName": "/packages/core/src/ng_template_outlet.ts",
"line": 7,
"character": 7
}
},
{
"name": "UserProfile",
"entryType": "undecorated_class",
"members": [
{
"name": "userId",
"type": "number",
"name": "ngTemplateOutletContext",
"type": "C",
"memberType": "property",
"memberTags": [],
"description": "A user identifier.",
"jsdocTags": []
"memberTags": ["input"],
"description": "A context object to attach to the {@link EmbeddedViewRef}. This should be an\nobject, the object's keys will be available for binding by the local template `let`\ndeclarations.\nUsing the key `$implicit` in the context object will set its value as default.",
"jsdocTags": [],
"inputAlias": "ngTemplateOutletContext",
"isRequiredInput": false
},
{
"name": "name",
"type": "string",
"memberType": "getter",
"memberTags": [],
"description": "Name of the user",
"jsdocTags": []
"name": "ngTemplateOutlet",
"type": "TemplateRef<C>",
"memberType": "property",
"memberTags": ["input"],
"description": "A string defining the template reference and optionally the context object for the template.",
"jsdocTags": [],
"inputAlias": "ngTemplateOutlet",
"isRequiredInput": false
},
{
"name": "name",
"type": "string",
"memberType": "setter",
"memberTags": [],
"description": "Name of the user",
"jsdocTags": []
"name": "ngTemplateOutletInjector",
"type": "Injector",
"memberType": "property",
"memberTags": ["input"],
"description": "Injector to be used within the embedded view.",
"jsdocTags": [],
"inputAlias": "ngTemplateOutletInjector",
"isRequiredInput": false
},
{
"params": [
"name": "ngOnChanges",
"signatures": [
{
"name": "config",
"description": "Setting for saving.",
"type": "object",
"isOptional": false,
"isRestParam": false
"name": "ngOnChanges",
"entryType": "function",
"description": "",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [
{
"name": "changes",
"description": "",
"type": "SimpleChanges",
"isOptional": false,
"isRestParam": false
}
],
"rawComment": "",
"returnType": "void"
}
],
"name": "save",
"returnType": "boolean",
"implementation": {
"params": [
{
"name": "changes",
"description": "",
"type": "SimpleChanges",
"isOptional": false,
"isRestParam": false
}
],
"isNewType": false,
"returnType": "void",
"generics": [],
"name": "ngOnChanges",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": ""
},
"entryType": "function",
"description": "Save the user.",
"jsdocTags": [
{
"name": "param",
"comment": "Setting for saving."
},
{
"name": "returns",
"comment": "Whether it succeeded"
}
],
"rawComment": "/**\n * Save the user.\n * @param config Setting for saving.\n * @returns Whether it succeeded\n */",
"description": "",
"jsdocTags": [],
"rawComment": "",
"memberType": "method",
"memberTags": []
}
],
"generics": [{"name": "C", "default": "unknown"}],
"description": "",
"jsdocTags": [
{"name": "ngModule", "comment": "CommonModule"},
{
"name": "description",
"comment": "Inserts an embedded view from a prepared `TemplateRef`.\n\nYou can attach a context object to the `EmbeddedViewRef` by setting `[ngTemplateOutletContext]`.\n`[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding\nby the local template `let` declarations."
},
{
"name": "usageNotes",
"comment": "```\n<ng-container *ngTemplateOutlet=\"templateRefExp; context: contextExp\"></ng-container>\n```\n\nUsing the key `$implicit` in the context object will set its value as default.\n\n### Example\n\n{@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}"
},
{"name": "publicApi", "comment": ""}
],
"rawComment": "/**\n * @ngModule CommonModule\n *\n * @description\n *\n * Inserts an embedded view from a prepared `TemplateRef`.\n *\n * You can attach a context object to the `EmbeddedViewRef` by setting `[ngTemplateOutletContext]`.\n * `[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding\n * by the local template `let` declarations.\n *\n * @usageNotes\n * ```\n * <ng-container *ngTemplateOutlet=\"templateRefExp; context: contextExp\"></ng-container>\n * ```\n *\n * Using the key `$implicit` in the context object will set its value as default.\n *\n * ### Example\n *\n * {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}\n *\n * @publicApi\n */",
"implements": ["OnChanges"],
"isStandalone": true,
"selector": "[ngTemplateOutlet]",
"exportAs": [],
"source": {
"filePath": "/packages/common/src/directives/ng_template_outlet.ts",
"startLine": 45,
"endLine": 126
}
},
{
"name": "ViewRef",
"isAbstract": true,
"entryType": "undecorated_class",
"members": [
{
"name": "destroy",
"signatures": [
{
"name": "destroy",
"entryType": "function",
"description": "Destroys this view and all of the data structures associated with it.",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * Destroys this view and all of the data structures associated with it.\n */",
"returnType": "void"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "void",
"generics": [],
"name": "destroy",
"description": "Destroys this view and all of the data structures associated with it.",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * Destroys this view and all of the data structures associated with it.\n */"
},
"entryType": "function",
"description": "Destroys this view and all of the data structures associated with it.",
"jsdocTags": [],
"rawComment": "/**\n * Destroys this view and all of the data structures associated with it.\n */",
"memberType": "method",
"memberTags": ["abstract"]
},
{
"name": "destroyed",
"type": "boolean",
"memberType": "getter",
"memberTags": ["abstract"],
"description": "Reports whether this view has been destroyed.",
"jsdocTags": [
{
"name": "returns",
"comment": "True after the `destroy()` method has been called, false otherwise."
}
]
},
{
"name": "onDestroy",
"signatures": [
{
"name": "onDestroy",
"entryType": "function",
"description": "A lifecycle hook that provides additional developer-defined cleanup\nfunctionality for views.",
"generics": [],
"isNewType": false,
"jsdocTags": [
{
"name": "param",
"comment": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked."
}
],
"params": [
{
"name": "callback",
"description": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked.",
"type": "Function",
"isOptional": false,
"isRestParam": false
}
],
"rawComment": "/**\n * A lifecycle hook that provides additional developer-defined cleanup\n * functionality for views.\n * @param callback A handler function that cleans up developer-defined data\n * associated with a view. Called when the `destroy()` method is invoked.\n */",
"returnType": "void"
}
],
"implementation": {
"params": [
{
"name": "callback",
"description": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked.",
"type": "Function",
"isOptional": false,
"isRestParam": false
}
],
"isNewType": false,
"returnType": "void",
"generics": [],
"name": "onDestroy",
"description": "A lifecycle hook that provides additional developer-defined cleanup\nfunctionality for views.",
"entryType": "function",
"jsdocTags": [
{
"name": "param",
"comment": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked."
}
],
"rawComment": "/**\n * A lifecycle hook that provides additional developer-defined cleanup\n * functionality for views.\n * @param callback A handler function that cleans up developer-defined data\n * associated with a view. Called when the `destroy()` method is invoked.\n */"
},
"entryType": "function",
"description": "A lifecycle hook that provides additional developer-defined cleanup\nfunctionality for views.",
"jsdocTags": [
{
"name": "param",
"comment": "A handler function that cleans up developer-defined data\nassociated with a view. Called when the `destroy()` method is invoked."
}
],
"rawComment": "/**\n * A lifecycle hook that provides additional developer-defined cleanup\n * functionality for views.\n * @param callback A handler function that cleans up developer-defined data\n * associated with a view. Called when the `destroy()` method is invoked.\n */",
"memberType": "method",
"memberTags": ["abstract"]
},
{
"name": "markForCheck",
"signatures": [
{
"name": "markForCheck",
"entryType": "function",
"description": "When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\nchange detection strategy, explicitly marks the view as changed so that\nit can be checked again.\n\nComponents are normally marked as dirty (in need of rerendering) when inputs\nhave changed or events have fired in the view. Call this method to ensure that\na component is checked even if these triggers have not occurred.\n\n<!-- TODO: Add a link to a chapter on OnPush components -->",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\n * change detection strategy, explicitly marks the view as changed so that\n * it can be checked again.\n *\n * Components are normally marked as dirty (in need of rerendering) when inputs\n * have changed or events have fired in the view. Call this method to ensure that\n * a component is checked even if these triggers have not occurred.\n *\n * <!-- TODO: Add a link to a chapter on OnPush components -->\n *\n */",
"returnType": "void"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "void",
"generics": [],
"name": "markForCheck",
"description": "When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\nchange detection strategy, explicitly marks the view as changed so that\nit can be checked again.\n\nComponents are normally marked as dirty (in need of rerendering) when inputs\nhave changed or events have fired in the view. Call this method to ensure that\na component is checked even if these triggers have not occurred.\n\n<!-- TODO: Add a link to a chapter on OnPush components -->",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\n * change detection strategy, explicitly marks the view as changed so that\n * it can be checked again.\n *\n * Components are normally marked as dirty (in need of rerendering) when inputs\n * have changed or events have fired in the view. Call this method to ensure that\n * a component is checked even if these triggers have not occurred.\n *\n * <!-- TODO: Add a link to a chapter on OnPush components -->\n *\n */"
},
"entryType": "function",
"description": "When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\nchange detection strategy, explicitly marks the view as changed so that\nit can be checked again.\n\nComponents are normally marked as dirty (in need of rerendering) when inputs\nhave changed or events have fired in the view. Call this method to ensure that\na component is checked even if these triggers have not occurred.\n\n<!-- TODO: Add a link to a chapter on OnPush components -->",
"jsdocTags": [],
"rawComment": "/**\n * When a view uses the {@link ChangeDetectionStrategy#OnPush} (checkOnce)\n * change detection strategy, explicitly marks the view as changed so that\n * it can be checked again.\n *\n * Components are normally marked as dirty (in need of rerendering) when inputs\n * have changed or events have fired in the view. Call this method to ensure that\n * a component is checked even if these triggers have not occurred.\n *\n * <!-- TODO: Add a link to a chapter on OnPush components -->\n *\n */",
"memberType": "method",
"memberTags": ["abstract", "override"]
},
{
"name": "detach",
"signatures": [
{
"name": "detach",
"entryType": "function",
"description": "Detaches this view from the change-detection tree.\nA detached view is not checked until it is reattached.\nUse in combination with `detectChanges()` to implement local change detection checks.\n\nDetached views are not checked during change detection runs until they are\nre-attached, even if they are marked as dirty.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * Detaches this view from the change-detection tree.\n * A detached view is not checked until it is reattached.\n * Use in combination with `detectChanges()` to implement local change detection checks.\n *\n * Detached views are not checked during change detection runs until they are\n * re-attached, even if they are marked as dirty.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */",
"returnType": "void"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "void",
"generics": [],
"name": "detach",
"description": "Detaches this view from the change-detection tree.\nA detached view is not checked until it is reattached.\nUse in combination with `detectChanges()` to implement local change detection checks.\n\nDetached views are not checked during change detection runs until they are\nre-attached, even if they are marked as dirty.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * Detaches this view from the change-detection tree.\n * A detached view is not checked until it is reattached.\n * Use in combination with `detectChanges()` to implement local change detection checks.\n *\n * Detached views are not checked during change detection runs until they are\n * re-attached, even if they are marked as dirty.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */"
},
"entryType": "function",
"description": "Detaches this view from the change-detection tree.\nA detached view is not checked until it is reattached.\nUse in combination with `detectChanges()` to implement local change detection checks.\n\nDetached views are not checked during change detection runs until they are\nre-attached, even if they are marked as dirty.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
"jsdocTags": [],
"rawComment": "/**\n * Detaches this view from the change-detection tree.\n * A detached view is not checked until it is reattached.\n * Use in combination with `detectChanges()` to implement local change detection checks.\n *\n * Detached views are not checked during change detection runs until they are\n * re-attached, even if they are marked as dirty.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */",
"memberType": "method",
"memberTags": ["abstract", "override"]
},
{
"name": "detectChanges",
"signatures": [
{
"name": "detectChanges",
"entryType": "function",
"description": "Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\nto implement local change detection checks.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\n * to implement local change detection checks.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */",
"returnType": "void"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "void",
"generics": [],
"name": "detectChanges",
"description": "Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\nto implement local change detection checks.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\n * to implement local change detection checks.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */"
},
"entryType": "function",
"description": "Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\nto implement local change detection checks.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n<!-- TODO: Add a live demo once ref.detectChanges is merged into master -->",
"jsdocTags": [],
"rawComment": "/**\n * Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach}\n * to implement local change detection checks.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->\n *\n */",
"memberType": "method",
"memberTags": ["abstract", "override"]
},
{
"name": "checkNoChanges",
"signatures": [
{
"name": "checkNoChanges",
"entryType": "function",
"description": "Checks the change detector and its children, and throws if any changes are detected.\n\nUse in development mode to verify that running change detection doesn't introduce\nother changes. Calling it in production mode is a noop.",
"generics": [],
"isNewType": false,
"jsdocTags": [
{
"name": "deprecated",
"comment": "This is a test-only API that does not have a place in production interface.\n`checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\nmode. For more granular `checkNoChanges` validation, use `ComponentFixture`."
}
],
"params": [],
"rawComment": "/**\n * Checks the change detector and its children, and throws if any changes are detected.\n *\n * Use in development mode to verify that running change detection doesn't introduce\n * other changes. Calling it in production mode is a noop.\n *\n * @deprecated This is a test-only API that does not have a place in production interface.\n * `checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\n * mode. For more granular `checkNoChanges` validation, use `ComponentFixture`.\n */",
"returnType": "void"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "void",
"generics": [],
"name": "checkNoChanges",
"description": "Checks the change detector and its children, and throws if any changes are detected.\n\nUse in development mode to verify that running change detection doesn't introduce\nother changes. Calling it in production mode is a noop.",
"entryType": "function",
"jsdocTags": [
{
"name": "deprecated",
"comment": "This is a test-only API that does not have a place in production interface.\n`checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\nmode. For more granular `checkNoChanges` validation, use `ComponentFixture`."
}
],
"rawComment": "/**\n * Checks the change detector and its children, and throws if any changes are detected.\n *\n * Use in development mode to verify that running change detection doesn't introduce\n * other changes. Calling it in production mode is a noop.\n *\n * @deprecated This is a test-only API that does not have a place in production interface.\n * `checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\n * mode. For more granular `checkNoChanges` validation, use `ComponentFixture`.\n */"
},
"entryType": "function",
"description": "Checks the change detector and its children, and throws if any changes are detected.\n\nUse in development mode to verify that running change detection doesn't introduce\nother changes. Calling it in production mode is a noop.",
"jsdocTags": [
{
"name": "deprecated",
"comment": "This is a test-only API that does not have a place in production interface.\n`checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\nmode. For more granular `checkNoChanges` validation, use `ComponentFixture`."
}
],
"rawComment": "/**\n * Checks the change detector and its children, and throws if any changes are detected.\n *\n * Use in development mode to verify that running change detection doesn't introduce\n * other changes. Calling it in production mode is a noop.\n *\n * @deprecated This is a test-only API that does not have a place in production interface.\n * `checkNoChanges` is already part of an `ApplicationRef` tick when the app is running in dev\n * mode. For more granular `checkNoChanges` validation, use `ComponentFixture`.\n */",
"memberType": "method",
"memberTags": ["abstract", "override"]
},
{
"name": "reattach",
"signatures": [
{
"name": "reattach",
"entryType": "function",
"description": "Re-attaches the previously detached view to the change detection tree.\nViews are attached to the tree by default.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
"rawComment": "/**\n * Re-attaches the previously detached view to the change detection tree.\n * Views are attached to the tree by default.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n *\n */",
"returnType": "void"
}
],
"implementation": {
"params": [],
"isNewType": false,
"returnType": "void",
"generics": [],
"name": "reattach",
"description": "Re-attaches the previously detached view to the change detection tree.\nViews are attached to the tree by default.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->",
"entryType": "function",
"jsdocTags": [],
"rawComment": "/**\n * Re-attaches the previously detached view to the change detection tree.\n * Views are attached to the tree by default.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n *\n */"
},
"entryType": "function",
"description": "Re-attaches the previously detached view to the change detection tree.\nViews are attached to the tree by default.\n\n<!-- TODO: Add a link to a chapter on detach/reattach/local digest -->",
"jsdocTags": [],
"rawComment": "/**\n * Re-attaches the previously detached view to the change detection tree.\n * Views are attached to the tree by default.\n *\n * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->\n *\n */",
"memberType": "method",
"memberTags": ["abstract", "override"]
}
],
"generics": [],
"description": "Represents an Angular view.",
"jsdocTags": [
{
"name": "see",
"comment": "[Change detection usage](/api/core/ChangeDetectorRef?tab=usage-notes)"
},
{"name": "publicApi", "comment": ""}
],
"rawComment": "/**\n * Represents an Angular view.\n *\n * @see [Change detection usage](/api/core/ChangeDetectorRef?tab=usage-notes)\n *\n * @publicApi\n */",
"extends": "ChangeDetectorRef",
"implements": [],
"source": {
"filePath": "/packages/core/src/linker/view_ref.ts",
"startLine": 18,
"endLine": 37
}
},
{
"name": "afterNextRender",
"signatures": [
{
"name": "afterNextRender",
"entryType": "function",
"description": "Register callbacks to be invoked the next time the application finishes rendering, during the\nspecified phases. The available phases are:\n- `earlyRead`\n Use this phase to **read** from the DOM before a subsequent `write` callback, for example to\n perform custom layout that the browser doesn't natively support. Prefer the `read` phase if\n reading can wait until after the write phase. **Never** write to the DOM in this phase.\n- `write`\n Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.\n- `mixedReadWrite`\n Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if\n it is possible to divide the work among the other phases instead.\n- `read`\n Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.\n\n<div class=\"alert is-critical\">\n\nYou should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite`\nphases when possible, to avoid performance degradation.\n\n</div>\n\nNote that:\n- Callbacks run in the following phase order *once, after the next render*:\n 1. `earlyRead`\n 2. `write`\n 3. `mixedReadWrite`\n 4. `read`\n- Callbacks in the same phase run in the order they are registered.\n- Callbacks run on browser platforms only, they will not run on the server.\n\nThe first phase callback to run as part of this spec will receive no parameters. Each\nsubsequent phase callback in this spec will receive the return value of the previously run\nphase callback as a parameter. This can be used to coordinate work across multiple phases.\n\nAngular is unable to verify or enforce that phases are used correctly, and instead\nrelies on each developer to follow the guidelines documented for each value and\ncarefully choose the appropriate one, refactoring their code if necessary. By doing\nso, Angular is better able to minimize the performance degradation associated with\nmanual DOM access, ensuring the best experience for the end users of your application\nor library.\n\n<div class=\"alert is-important\">\n\nComponents are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\nYou must use caution when directly reading or writing the DOM and layout.\n\n</div>",
"generics": [
{"name": "E", "default": "never"},
{"name": "W", "default": "never"},
{"name": "M", "default": "never"}
],
"isNewType": false,
"jsdocTags": [
{"name": "param", "comment": "The callback functions to register"},
{"name": "param", "comment": "Options to control the behavior of the callback"},
{
"name": "usageNotes",
"comment": "Use `afterNextRender` to read or write the DOM once,\nfor example to initialize a non-Angular library.\n\n### Example\n```ts\n@Component({\n selector: 'my-chart-cmp',\n template: `<div #chart>{{ ... }}</div>`,\n})\nexport class MyChartCmp {\n @ViewChild('chart') chartRef: ElementRef;\n chart: MyChart|null;\n\n constructor() {\n afterNextRender({\n write: () => {\n this.chart = new MyChart(this.chartRef.nativeElement);\n }\n });\n }\n}\n```"
},
{"name": "developerPreview", "comment": ""}
],
"params": [
{
"name": "spec",
"description": "The callback functions to register",
"type": "{ earlyRead?: () => E; write?: (...args: [E] extends [never] ? [] : [E]) => W; mixedReadWrite?: (...args: [W] extends [never] ? [E] extends [never] ? [] : [E] : [W]) => M; read?: (...args: [...] extends [...] ? [...] extends [...] ? [...] extends [...] ? [] : [...] : [...] : [...]) => void; }",
"isOptional": false,
"isRestParam": false
},
{
"name": "options",
"description": "Options to control the behavior of the callback",
"type": "Omit<AfterRenderOptions, \"phase\">",
"isOptional": true,
"isRestParam": false
}
],
"rawComment": "/**\n * Register callbacks to be invoked the next time the application finishes rendering, during the\n * specified phases. The available phases are:\n * - `earlyRead`\n * Use this phase to **read** from the DOM before a subsequent `write` callback, for example to\n * perform custom layout that the browser doesn't natively support. Prefer the `read` phase if\n * reading can wait until after the write phase. **Never** write to the DOM in this phase.\n * - `write`\n * Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.\n * - `mixedReadWrite`\n * Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if\n * it is possible to divide the work among the other phases instead.\n * - `read`\n * Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.\n *\n * <div class=\"alert is-critical\">\n *\n * You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite`\n * phases when possible, to avoid performance degradation.\n *\n * </div>\n *\n * Note that:\n * - Callbacks run in the following phase order *once, after the next render*:\n * 1. `earlyRead`\n * 2. `write`\n * 3. `mixedReadWrite`\n * 4. `read`\n * - Callbacks in the same phase run in the order they are registered.\n * - Callbacks run on browser platforms only, they will not run on the server.\n *\n * The first phase callback to run as part of this spec will receive no parameters. Each\n * subsequent phase callback in this spec will receive the return value of the previously run\n * phase callback as a parameter. This can be used to coordinate work across multiple phases.\n *\n * Angular is unable to verify or enforce that phases are used correctly, and instead\n * relies on each developer to follow the guidelines documented for each value and\n * carefully choose the appropriate one, refactoring their code if necessary. By doing\n * so, Angular is better able to minimize the performance degradation associated with\n * manual DOM access, ensuring the best experience for the end users of your application\n * or library.\n *\n * <div class=\"alert is-important\">\n *\n * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\n * You must use caution when directly reading or writing the DOM and layout.\n *\n * </div>\n *\n * @param spec The callback functions to register\n * @param options Options to control the behavior of the callback\n *\n * @usageNotes\n *\n * Use `afterNextRender` to read or write the DOM once,\n * for example to initialize a non-Angular library.\n *\n * ### Example\n * ```ts\n * @Component({\n * selector: 'my-chart-cmp',\n * template: `<div #chart>{{ ... }}</div>`,\n * })\n * export class MyChartCmp {\n * @ViewChild('chart') chartRef: ElementRef;\n * chart: MyChart|null;\n *\n * constructor() {\n * afterNextRender({\n * write: () => {\n * this.chart = new MyChart(this.chartRef.nativeElement);\n * }\n * });\n * }\n * }\n * ```\n *\n * @developerPreview\n */",
"returnType": "AfterRenderRef"
},
{
"name": "afterNextRender",
"entryType": "function",
"description": "Register a callback to be invoked the next time the application finishes rendering, during the\n`mixedReadWrite` phase.\n\n<div class=\"alert is-critical\">\n\nYou should prefer specifying an explicit phase for the callback instead, or you risk significant\nperformance degradation.\n\n</div>\n\nNote that the callback will run\n- in the order it was registered\n- on browser platforms only\n- during the `mixedReadWrite` phase\n\n<div class=\"alert is-important\">\n\nComponents are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\nYou must use caution when directly reading or writing the DOM and layout.\n\n</div>",
"generics": [],
"isNewType": false,
"jsdocTags": [
{"name": "param", "comment": "A callback function to register"},
{"name": "param", "comment": "Options to control the behavior of the callback"},
{
"name": "usageNotes",
"comment": "Use `afterNextRender` to read or write the DOM once,\nfor example to initialize a non-Angular library.\n\n### Example\n```ts\n@Component({\n selector: 'my-chart-cmp',\n template: `<div #chart>{{ ... }}</div>`,\n})\nexport class MyChartCmp {\n @ViewChild('chart') chartRef: ElementRef;\n chart: MyChart|null;\n\n constructor() {\n afterNextRender({\n write: () => {\n this.chart = new MyChart(this.chartRef.nativeElement);\n }\n });\n }\n}\n```"
},
{"name": "developerPreview", "comment": ""}
],
"params": [
{
"name": "callback",
"description": "A callback function to register",
"type": "VoidFunction",
"isOptional": false,
"isRestParam": false
},
{
"name": "options",
"description": "Options to control the behavior of the callback",
"type": "AfterRenderOptions",
"isOptional": true,
"isRestParam": false
}
],
"rawComment": "/**\n * Register a callback to be invoked the next time the application finishes rendering, during the\n * `mixedReadWrite` phase.\n *\n * <div class=\"alert is-critical\">\n *\n * You should prefer specifying an explicit phase for the callback instead, or you risk significant\n * performance degradation.\n *\n * </div>\n *\n * Note that the callback will run\n * - in the order it was registered\n * - on browser platforms only\n * - during the `mixedReadWrite` phase\n *\n * <div class=\"alert is-important\">\n *\n * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.\n * You must use caution when directly reading or writing the DOM and layout.\n *\n * </div>\n *\n * @param callback A callback function to register\n * @param options Options to control the behavior of the callback\n *\n * @usageNotes\n *\n * Use `afterNextRender` to read or write the DOM once,\n * for example to initialize a non-Angular library.\n *\n * ### Example\n * ```ts\n * @Component({\n * selector: 'my-chart-cmp',\n * template: `<div #chart>{{ ... }}</div>`,\n * })\n * export class MyChartCmp {\n * @ViewChild('chart') chartRef: ElementRef;\n * chart: MyChart|null;\n *\n * constructor() {\n * afterNextRender({\n * write: () => {\n * this.chart = new MyChart(this.chartRef.nativeElement);\n * }\n * });\n * }\n * }\n * ```\n *\n * @developerPreview\n */",
"returnType": "AfterRenderRef"
}
],
"implementation": {
"params": [
{
"name": "callbackOrSpec",
"description": "",
"type": "VoidFunction | { earlyRead?: () => unknown; write?: (r?: unknown) => unknown; mixedReadWrite?: (r?: unknown) => unknown; read?: (r?: unknown) => void; }",
"isOptional": false,
"isRestParam": false
},
{
"name": "options",
"description": "",
"type": "AfterRenderOptions",
"isOptional": true,
"isRestParam": false
}
],
"isNewType": false,
"returnType": "AfterRenderRef",
"generics": [],
"name": "afterNextRender",
"description": "",
"entryType": "function",
"jsdocTags": [],
"rawComment": ""
},
"entryType": "function",
"description": "",
"jsdocTags": [],
"rawComment": "",
"source": {
"fileName": "/packages/core/src/user-profile.ts",
"line": 3,
"character": 7
"filePath": "/packages/core/src/render3/after_render_hooks.ts",
"startLine": 442,
"endLine": 450
}
},
{
"name": "provideClientHydration",
"signatures": [
{
"name": "provideClientHydration",
"entryType": "function",
"description": "Sets up providers necessary to enable hydration functionality for the application.\n\nBy default, the function enables the recommended set of features for the optimal\nperformance for most of the applications. It includes the following features:\n\n* Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n* [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\ntransferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n[here](guide/ssr#caching-data-when-using-httpclient).\n\nThese functions allow you to disable some of the default features or enable new ones:\n\n* {@link withNoHttpTransferCache} to disable HTTP transfer cache\n* {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n* {@link withI18nSupport} to enable hydration support for i18n blocks\n* {@link withEventReplay} to enable support for replaying user events",
"generics": [],
"isNewType": false,
"jsdocTags": [
{
"name": "usageNotes",
"comment": "Basic example of how you can enable hydration in your application when\n`bootstrapApplication` function is used:\n```\nbootstrapApplication(AppComponent, {\n providers: [provideClientHydration()]\n});\n```\n\nAlternatively if you are using NgModules, you would add `provideClientHydration`\nto your root app module's provider list.\n```\n@NgModule({\n declarations: [RootCmp],\n bootstrap: [RootCmp],\n providers: [provideClientHydration()],\n})\nexport class AppModule {}\n```"
},
{"name": "see", "comment": "{@link withNoHttpTransferCache}"},
{"name": "see", "comment": "{@link withHttpTransferCacheOptions}"},
{"name": "see", "comment": "{@link withI18nSupport}"},
{"name": "see", "comment": "{@link withEventReplay}"},
{
"name": "param",
"comment": "Optional features to configure additional router behaviors."
},
{"name": "returns", "comment": "A set of providers to enable hydration."},
{"name": "publicApi", "comment": ""}
],
"params": [
{
"name": "features",
"description": "Optional features to configure additional router behaviors.",
"type": "HydrationFeature<HydrationFeatureKind>[]",
"isOptional": false,
"isRestParam": true
}
],
"rawComment": "/**\n * Sets up providers necessary to enable hydration functionality for the application.\n *\n * By default, the function enables the recommended set of features for the optimal\n * performance for most of the applications. It includes the following features:\n *\n * * Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n * * [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\n * transferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n * [here](guide/ssr#caching-data-when-using-httpclient).\n *\n * These functions allow you to disable some of the default features or enable new ones:\n *\n * * {@link withNoHttpTransferCache} to disable HTTP transfer cache\n * * {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n * * {@link withI18nSupport} to enable hydration support for i18n blocks\n * * {@link withEventReplay} to enable support for replaying user events\n *\n * @usageNotes\n *\n * Basic example of how you can enable hydration in your application when\n * `bootstrapApplication` function is used:\n * ```\n * bootstrapApplication(AppComponent, {\n * providers: [provideClientHydration()]\n * });\n * ```\n *\n * Alternatively if you are using NgModules, you would add `provideClientHydration`\n * to your root app module's provider list.\n * ```\n * @NgModule({\n * declarations: [RootCmp],\n * bootstrap: [RootCmp],\n * providers: [provideClientHydration()],\n * })\n * export class AppModule {}\n * ```\n *\n * @see {@link withNoHttpTransferCache}\n * @see {@link withHttpTransferCacheOptions}\n * @see {@link withI18nSupport}\n * @see {@link withEventReplay}\n *\n * @param features Optional features to configure additional router behaviors.\n * @returns A set of providers to enable hydration.\n *\n * @publicApi\n */",
"returnType": "EnvironmentProviders"
}
],
"implementation": {
"params": [
{
"name": "features",
"description": "Optional features to configure additional router behaviors.",
"type": "HydrationFeature<HydrationFeatureKind>[]",
"isOptional": false,
"isRestParam": true
}
],
"isNewType": false,
"returnType": "EnvironmentProviders",
"returnDescription": "A set of providers to enable hydration.",
"generics": [],
"name": "provideClientHydration",
"description": "Sets up providers necessary to enable hydration functionality for the application.\n\nBy default, the function enables the recommended set of features for the optimal\nperformance for most of the applications. It includes the following features:\n\n* Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n* [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\ntransferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n[here](guide/ssr#caching-data-when-using-httpclient).\n\nThese functions allow you to disable some of the default features or enable new ones:\n\n* {@link withNoHttpTransferCache} to disable HTTP transfer cache\n* {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n* {@link withI18nSupport} to enable hydration support for i18n blocks\n* {@link withEventReplay} to enable support for replaying user events",
"entryType": "function",
"jsdocTags": [
{
"name": "usageNotes",
"comment": "Basic example of how you can enable hydration in your application when\n`bootstrapApplication` function is used:\n```\nbootstrapApplication(AppComponent, {\n providers: [provideClientHydration()]\n});\n```\n\nAlternatively if you are using NgModules, you would add `provideClientHydration`\nto your root app module's provider list.\n```\n@NgModule({\n declarations: [RootCmp],\n bootstrap: [RootCmp],\n providers: [provideClientHydration()],\n})\nexport class AppModule {}\n```"
},
{"name": "see", "comment": "{@link withNoHttpTransferCache}"},
{"name": "see", "comment": "{@link withHttpTransferCacheOptions}"},
{"name": "see", "comment": "{@link withI18nSupport}"},
{"name": "see", "comment": "{@link withEventReplay}"},
{
"name": "param",
"comment": "Optional features to configure additional router behaviors."
},
{"name": "returns", "comment": "A set of providers to enable hydration."},
{"name": "publicApi", "comment": ""}
],
"rawComment": "/**\n * Sets up providers necessary to enable hydration functionality for the application.\n *\n * By default, the function enables the recommended set of features for the optimal\n * performance for most of the applications. It includes the following features:\n *\n * * Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n * * [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\n * transferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n * [here](guide/ssr#caching-data-when-using-httpclient).\n *\n * These functions allow you to disable some of the default features or enable new ones:\n *\n * * {@link withNoHttpTransferCache} to disable HTTP transfer cache\n * * {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n * * {@link withI18nSupport} to enable hydration support for i18n blocks\n * * {@link withEventReplay} to enable support for replaying user events\n *\n * @usageNotes\n *\n * Basic example of how you can enable hydration in your application when\n * `bootstrapApplication` function is used:\n * ```\n * bootstrapApplication(AppComponent, {\n * providers: [provideClientHydration()]\n * });\n * ```\n *\n * Alternatively if you are using NgModules, you would add `provideClientHydration`\n * to your root app module's provider list.\n * ```\n * @NgModule({\n * declarations: [RootCmp],\n * bootstrap: [RootCmp],\n * providers: [provideClientHydration()],\n * })\n * export class AppModule {}\n * ```\n *\n * @see {@link withNoHttpTransferCache}\n * @see {@link withHttpTransferCacheOptions}\n * @see {@link withI18nSupport}\n * @see {@link withEventReplay}\n *\n * @param features Optional features to configure additional router behaviors.\n * @returns A set of providers to enable hydration.\n *\n * @publicApi\n */"
},
"entryType": "function",
"description": "Sets up providers necessary to enable hydration functionality for the application.\n\nBy default, the function enables the recommended set of features for the optimal\nperformance for most of the applications. It includes the following features:\n\n* Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n* [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\ntransferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n[here](guide/ssr#caching-data-when-using-httpclient).\n\nThese functions allow you to disable some of the default features or enable new ones:\n\n* {@link withNoHttpTransferCache} to disable HTTP transfer cache\n* {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n* {@link withI18nSupport} to enable hydration support for i18n blocks\n* {@link withEventReplay} to enable support for replaying user events",
"jsdocTags": [
{
"name": "usageNotes",
"comment": "Basic example of how you can enable hydration in your application when\n`bootstrapApplication` function is used:\n```\nbootstrapApplication(AppComponent, {\n providers: [provideClientHydration()]\n});\n```\n\nAlternatively if you are using NgModules, you would add `provideClientHydration`\nto your root app module's provider list.\n```\n@NgModule({\n declarations: [RootCmp],\n bootstrap: [RootCmp],\n providers: [provideClientHydration()],\n})\nexport class AppModule {}\n```"
},
{"name": "see", "comment": "{@link withNoHttpTransferCache}"},
{"name": "see", "comment": "{@link withHttpTransferCacheOptions}"},
{"name": "see", "comment": "{@link withI18nSupport}"},
{"name": "see", "comment": "{@link withEventReplay}"},
{"name": "param", "comment": "Optional features to configure additional router behaviors."},
{"name": "returns", "comment": "A set of providers to enable hydration."},
{"name": "publicApi", "comment": ""}
],
"rawComment": "/**\n * Sets up providers necessary to enable hydration functionality for the application.\n *\n * By default, the function enables the recommended set of features for the optimal\n * performance for most of the applications. It includes the following features:\n *\n * * Reconciling DOM hydration. Learn more about it [here](guide/hydration).\n * * [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and\n * transferring this cache to the client to avoid extra HTTP requests. Learn more about data caching\n * [here](guide/ssr#caching-data-when-using-httpclient).\n *\n * These functions allow you to disable some of the default features or enable new ones:\n *\n * * {@link withNoHttpTransferCache} to disable HTTP transfer cache\n * * {@link withHttpTransferCacheOptions} to configure some HTTP transfer cache options\n * * {@link withI18nSupport} to enable hydration support for i18n blocks\n * * {@link withEventReplay} to enable support for replaying user events\n *\n * @usageNotes\n *\n * Basic example of how you can enable hydration in your application when\n * `bootstrapApplication` function is used:\n * ```\n * bootstrapApplication(AppComponent, {\n * providers: [provideClientHydration()]\n * });\n * ```\n *\n * Alternatively if you are using NgModules, you would add `provideClientHydration`\n * to your root app module's provider list.\n * ```\n * @NgModule({\n * declarations: [RootCmp],\n * bootstrap: [RootCmp],\n * providers: [provideClientHydration()],\n * })\n * export class AppModule {}\n * ```\n *\n * @see {@link withNoHttpTransferCache}\n * @see {@link withHttpTransferCacheOptions}\n * @see {@link withI18nSupport}\n * @see {@link withEventReplay}\n *\n * @param features Optional features to configure additional router behaviors.\n * @returns A set of providers to enable hydration.\n *\n * @publicApi\n */",
"source": {
"filePath": "/packages/platform-browser/src/hydration.ts",
"startLine": 201,
"endLine": 238
}
},
{
"name": "AfterRenderOptions",
"isAbstract": false,
"entryType": "interface",
"members": [
{
"name": "injector",
"type": "Injector",
"memberType": "property",
"memberTags": ["optional"],
"description": "The `Injector` to use during creation.\n\nIf this is not provided, the current injection context will be used instead (via `inject`).",
"jsdocTags": []
},
{
"name": "phase",
"type": "AfterRenderPhase",
"memberType": "property",
"memberTags": ["optional"],
"description": "The phase the callback should be invoked in.\n\n<div class=\"alert is-critical\">\n\nDefaults to `AfterRenderPhase.MixedReadWrite`. You should choose a more specific\nphase instead. See `AfterRenderPhase` for more information.\n\n</div>",
"jsdocTags": [
{
"name": "deprecated",
"comment": "Specify the phase for your callback to run in by passing a spec-object as the first\nparameter to `afterRender` or `afterNextRender` instead of a function."
}
]
}
],
"generics": [],
"description": "Options passed to `afterRender` and `afterNextRender`.",
"jsdocTags": [{"name": "developerPreview", "comment": ""}],
"rawComment": "/**\n * Options passed to `afterRender` and `afterNextRender`.\n *\n * @developerPreview\n */",
"implements": [],
"source": {
"filePath": "/packages/core/src/render3/after_render_hooks.ts",
"startLine": 103,
"endLine": 125
}
}
]
@@ -0,0 +1,77 @@
import {runfiles} from '@bazel/runfiles';
import {readFile} from 'fs/promises';
import {JSDOM} from 'jsdom';
import {renderEntry} from '../rendering';
import {getRenderable} from '../processing';
import {initHighlighter} from '../shiki/shiki';
import {configureMarkedGlobally} from '../marked/configuration';
import {setSymbols} from '../symbol-context';
// Note: The tests will probably break if the schema of the api extraction changes.
// All entries in the fake-entries are extracted from Angular's api.
// You can just generate them an copy/replace the items in the fake-entries file.
describe('markdown to html', () => {
const entries = new Map<string, DocumentFragment>();
const entries2 = new Map<string, string>();
beforeAll(async () => {
await initHighlighter();
await configureMarkedGlobally();
const entryContent = await readFile(runfiles.resolvePackageRelative('fake-entries.json'), {
encoding: 'utf-8',
});
const entryJson = JSON.parse(entryContent) as any;
const symbols = new Map<string, string>([
['AfterRenderPhase', 'core'],
['afterRender', 'core'],
]);
setSymbols(symbols);
for (const entry of entryJson.entries) {
const renderableJson = getRenderable(entry, '@angular/fakeentry');
const fragment = JSDOM.fragment(await renderEntry(renderableJson));
entries.set(entry['name'], fragment);
entries2.set(entry['name'], await renderEntry(renderableJson));
}
});
it('should render description correctly', () => {
const afterNextRenderEntry = entries.get('afterNextRender')!;
const header = afterNextRenderEntry.querySelector('.docs-reference-header')!;
expect(header).toBeDefined();
expect(header.outerHTML).not.toContain('```');
console.log(entries2.get('afterNextRender'));
const list = afterNextRenderEntry.querySelector('ul')!;
expect(list).toBeDefined();
// List are rendered
expect(list.outerHTML).toContain('<li>');
// Code blocks are rendered
expect(list.outerHTML).toContain('<code>mixedReadWrite</code>');
});
it('should render multiple {@link} blocks', () => {
const provideClientHydrationEntry = entries.get('provideClientHydration')!;
expect(provideClientHydrationEntry).toBeDefined();
const cardItem = provideClientHydrationEntry.querySelector('.docs-reference-card-item')!;
expect(cardItem.innerHTML).not.toContain('@link');
});
it('should create cross-links', () => {
const entry = entries.get('AfterRenderOptions')!;
expect(entry).toBeDefined();
// In the description
const descriptionItem = entry.querySelector('.docs-reference-description')!;
expect(descriptionItem.innerHTML).toContain('<a href="/api/core/afterRender">afterRender</a>');
// In the card
const cardItem = entry.querySelectorAll('.docs-reference-card-item')[1];
expect(cardItem.innerHTML).toContain(
'<a href="/api/core/AfterRenderPhase#MixedReadWrite">AfterRenderPhase.MixedReadWrite</a>',
);
});
});
@@ -1,8 +1,21 @@
import {setCurrentSymbol, setSymbols} from '../../symbol-context';
import {addHtmlAdditionalLinks} from '../../transforms/jsdoc-transforms';
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
describe('jsdoc transforms', () => {
it('should transform links', () => {
setCurrentSymbol('Router');
setSymbols(
new Map([
['Route', 'test'],
['Router', 'test'],
['Router.someMethod', 'test'],
['Router.someMethodWithParenthesis', 'test'],
['FormGroup', 'test'],
['FormGroup.someMethod', 'test'],
]),
);
const entry = addHtmlAdditionalLinks({
jsdocTags: [
{
@@ -21,6 +34,30 @@ describe('jsdoc transforms', () => {
name: 'see',
comment: '{@link Route Something else}',
},
{
name: 'see',
comment: '{@link #someMethod}',
},
{
name: 'see',
comment: '{@link #someMethodWithParenthesis()}',
},
{
name: 'see',
comment: '{@link someMethod()}',
},
{
name: 'see',
comment: '{@link FormGroup.someMethod()}',
},
{
name: 'see',
comment: '{@link https://angular.dev/api/core/ApplicationRef}',
},
{
name: 'see',
comment: '{@link https://angular.dev}',
},
],
moduleName: 'test',
});
@@ -39,12 +76,38 @@ describe('jsdoc transforms', () => {
expect(entry.additionalLinks[2]).toEqual({
label: 'Route',
url: 'api/test/Route',
url: '/api/test/Route',
});
expect(entry.additionalLinks[3]).toEqual({
label: 'Something else',
url: 'api/test/Route',
url: '/api/test/Route',
});
expect(entry.additionalLinks[4]).toEqual({
label: 'someMethod',
url: '/api/test/Router#someMethod',
});
expect(entry.additionalLinks[5]).toEqual({
label: 'someMethodWithParenthesis()',
url: '/api/test/Router#someMethodWithParenthesis',
});
expect(entry.additionalLinks[6]).toEqual({
label: 'someMethod()',
url: '/api/test/Router#someMethod',
});
expect(entry.additionalLinks[7]).toEqual({
label: 'FormGroup.someMethod()',
url: '/api/test/FormGroup#someMethod',
});
expect(entry.additionalLinks[8]).toEqual({
label: 'ApplicationRef',
url: 'https://angular.dev/api/core/ApplicationRef',
});
expect(entry.additionalLinks[9]).toEqual({
label: 'angular.dev',
url: 'https://angular.dev',
});
});
});
@@ -16,7 +16,7 @@ import {
addHtmlUsageNotes,
setEntryFlags,
} from './jsdoc-transforms';
import {addRenderableGroupMembers} from './member-transforms';
import {addRenderableMembers} from './member-transforms';
import {addModuleName} from './module-name';
/** Given an unprocessed class entry, get the fully renderable class entry. */
@@ -26,7 +26,7 @@ export function getClassRenderable(
): ClassEntryRenderable {
return setEntryFlags(
addRenderableCodeToc(
addRenderableGroupMembers(
addRenderableMembers(
addHtmlAdditionalLinks(
addHtmlUsageNotes(
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(classEntry, moduleName))),
@@ -62,7 +62,7 @@ function getArgumentsLabel(command: CliCommand): string {
if (args.length === 0) {
return '';
}
return command.command.replace(`ng ${command.name} `, '');
return command.command.replace(`${command.name} `, '');
}
function getArgs(command: CliCommand): CliOption[] {
@@ -8,18 +8,18 @@
import {
DocEntry,
FunctionEntry,
FunctionSignatureMetadata,
MemberEntry,
MemberTags,
ParameterEntry,
PropertyEntry,
isFunctionEntryWithOverloads,
} from '../entities';
import {
isClassEntry,
isClassMethodEntry,
isConstantEntry,
isDecoratorEntry,
isDeprecatedEntry,
isEnumEntry,
isFunctionEntry,
@@ -31,9 +31,11 @@ import {
} from '../entities/categorization';
import {CodeLineRenderable} from '../entities/renderables';
import {HasModuleName, HasRenderableToc} from '../entities/traits';
import {codeToHtml} from '../shiki/shiki';
import {getModuleName} from '../symbol-context';
import {codeToHtml, replaceKeywordFromShikiHtml} from '../shiki/shiki';
import {filterLifecycleMethods, mergeGettersAndSetters} from './member-transforms';
import {getLinkToModule} from './url-transforms';
// Allows to generate links for code lines.
interface CodeTableOfContentsData {
@@ -64,7 +66,19 @@ export function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
const metadata = mapDocEntryToCode(entry);
appendPrefixAndSuffix(entry, metadata);
const codeWithSyntaxHighlighting = codeToHtml(metadata.contents, 'typescript');
let codeWithSyntaxHighlighting = codeToHtml(metadata.contents, 'typescript', {
removeFunctionKeyword: true,
});
if (isDecoratorEntry(entry)) {
// Shiki requires a keyword for correct formating of Decorators
// We use an interface and then replace it with a '@'
codeWithSyntaxHighlighting = replaceKeywordFromShikiHtml(
'interface',
codeWithSyntaxHighlighting,
'@',
);
}
// shiki returns the lines wrapped by 2 node : 1 pre node, 1 code node.
// As leveraging jsdom isn't trivial here, we rely on a regex to extract the line nodes
@@ -77,8 +91,13 @@ export function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
const insideCode = match[2];
const afterCode = match[3];
const lines = splitLines(insideCode);
const groups = groupCodeLines(lines, metadata);
// Note: Don't expect enum value in signatures to be linked correctly
// as skihi already splits them into separate span blocks.
// Only the enum itself will recieve a link
const codeWithLinks = addApiLinksToHtml(insideCode);
const lines = splitLines(codeWithLinks);
const groups = groupCodeLines(lines, metadata, entry);
return {
...entry,
@@ -89,11 +108,12 @@ export function addRenderableCodeToc<T extends DocEntry & HasModuleName>(
}
/** Group overloaded methods */
function groupCodeLines(lines: string[], metadata: CodeTableOfContentsData) {
function groupCodeLines(lines: string[], metadata: CodeTableOfContentsData, entry: DocEntry) {
const hasSingleSignature = isFunctionEntry(entry) && entry.signatures.length === 1;
return lines.reduce((groups, line, index) => {
const tocItem = {
const tocItem: CodeLineRenderable = {
contents: line,
id: metadata.codeLineNumbersWithIdentifiers.get(index),
id: hasSingleSignature ? undefined : metadata.codeLineNumbersWithIdentifiers.get(index),
isDeprecated: metadata.deprecatedLineNumbers.some((lineNumber) => lineNumber === index),
};
@@ -109,46 +129,56 @@ function groupCodeLines(lines: string[], metadata: CodeTableOfContentsData) {
}
export function mapDocEntryToCode(entry: DocEntry): CodeTableOfContentsData {
const isDeprecated = isDeprecatedEntry(entry);
const deprecatedLineNumbers = isDeprecated ? [0] : [];
if (isClassEntry(entry)) {
const members = filterLifecycleMethods(mergeGettersAndSetters(entry.members));
return getCodeTocData(members, true);
return getCodeTocData(members, true, isDeprecated);
}
if (isDecoratorEntry(entry)) {
return getCodeTocData(entry.members, true, isDeprecated);
}
if (isConstantEntry(entry)) {
const isDeprecated = isDeprecatedEntry(entry);
return {
contents: `const ${entry.name}: ${entry.type};`,
codeLineNumbersWithIdentifiers: new Map(),
deprecatedLineNumbers: isDeprecated ? [0] : [],
deprecatedLineNumbers,
};
}
if (isEnumEntry(entry)) {
return getCodeTocData(entry.members, true);
return getCodeTocData(entry.members, true, isDeprecated);
}
if (isInterfaceEntry(entry)) {
return getCodeTocData(mergeGettersAndSetters(entry.members), true);
return getCodeTocData(mergeGettersAndSetters(entry.members), true, isDeprecated);
}
if (isFunctionEntry(entry)) {
const isDeprecated = isDeprecatedEntry(entry);
const codeLineNumbersWithIdentifiers = new Map<number, string>();
const hasSingleSignature = entry.signatures.length === 1;
if (isFunctionEntryWithOverloads(entry) && entry.overloads) {
if (entry.signatures.length > 0) {
const initialMetadata: CodeTableOfContentsData = {
contents: '',
codeLineNumbersWithIdentifiers: new Map<number, string>(),
deprecatedLineNumbers: [],
deprecatedLineNumbers,
};
return entry.overloads.reduce(
(acc: CodeTableOfContentsData, curr: FunctionEntry, index: number) => {
return entry.signatures.reduce(
(acc: CodeTableOfContentsData, curr: FunctionSignatureMetadata, index: number) => {
const lineNumber = index;
acc.codeLineNumbersWithIdentifiers.set(lineNumber, curr.name);
acc.contents += `${curr.name}(${curr.params
.map((param) => mapParamEntry(param))
.join(`, `)}): ${curr.returnType}\n`;
acc.codeLineNumbersWithIdentifiers.set(lineNumber, `${curr.name}_${index}`);
acc.contents += getMethodCodeLine(curr, [], hasSingleSignature, true);
// We don't want to add line break after the last item
if (!hasSingleSignature && index < entry.signatures.length - 1) {
acc.contents += '\n';
}
if (isDeprecatedEntry(curr)) {
acc.deprecatedLineNumbers.push(lineNumber);
}
@@ -157,11 +187,12 @@ export function mapDocEntryToCode(entry: DocEntry): CodeTableOfContentsData {
initialMetadata,
);
}
return {
// It is important to add the function keyword as shiki will only highlight valid ts
contents: `function ${getMethodCodeLine(entry, [], true)}`,
contents: `function ${getMethodCodeLine(entry.implementation, [], true)}`,
codeLineNumbersWithIdentifiers,
deprecatedLineNumbers: isDeprecated ? [0] : [],
deprecatedLineNumbers,
};
}
@@ -206,16 +237,13 @@ export function mapDocEntryToCode(entry: DocEntry): CodeTableOfContentsData {
return {
contents: lines.join('\n'),
codeLineNumbersWithIdentifiers,
deprecatedLineNumbers: [],
deprecatedLineNumbers,
};
}
if (isTypeAliasEntry(entry)) {
const isDeprecated = isDeprecatedEntry(entry);
const contents = `type ${entry.name} = ${entry.type}`;
let deprecatedLineNumbers = [];
if (isDeprecated) {
const numberOfLinesOfCode = getNumberOfLinesOfCode(contents);
@@ -234,36 +262,54 @@ export function mapDocEntryToCode(entry: DocEntry): CodeTableOfContentsData {
return {
contents: '',
codeLineNumbersWithIdentifiers: new Map(),
deprecatedLineNumbers: [],
deprecatedLineNumbers,
};
}
/** Generate code ToC data for list of members. */
function getCodeTocData(members: MemberEntry[], hasPrefixLine: boolean): CodeTableOfContentsData {
function getCodeTocData(
members: MemberEntry[],
hasPrefixLine: boolean,
isDeprecated: boolean,
): CodeTableOfContentsData {
const initialMetadata: CodeTableOfContentsData = {
contents: '',
codeLineNumbersWithIdentifiers: new Map<number, string>(),
deprecatedLineNumbers: [],
deprecatedLineNumbers: isDeprecated ? [0] : [],
};
// In case when hasPrefixLine is true we should take it into account when we're generating
// `codeLineNumbersWithIdentifiers` below.
const skip = !!hasPrefixLine ? 1 : 0;
let lineNumber = skip;
return members.reduce((acc: CodeTableOfContentsData, curr: MemberEntry, index: number) => {
const lineNumber = index + skip;
acc.codeLineNumbersWithIdentifiers.set(lineNumber, curr.name);
acc.contents += ` ${getCodeLine(curr).trim()}\n`;
if (isDeprecatedEntry(curr)) {
acc.deprecatedLineNumbers.push(lineNumber);
const setTocData = (entry: DocEntry | MemberEntry, content: string) => {
acc.contents += ` ${content.trim()}\n`;
acc.codeLineNumbersWithIdentifiers.set(lineNumber, entry.name);
if (isDeprecatedEntry(entry)) {
acc.deprecatedLineNumbers.push(lineNumber);
}
lineNumber++;
};
if (isClassMethodEntry(curr)) {
if (curr.signatures.length > 0) {
curr.signatures.forEach((signature) => {
setTocData(signature, getMethodCodeLine(signature, curr.memberTags));
});
} else {
setTocData(curr, getMethodCodeLine(curr.implementation, curr.memberTags));
}
} else {
setTocData(curr, getCodeLine(curr));
}
return acc;
}, initialMetadata);
}
function getCodeLine(member: MemberEntry) {
if (isClassMethodEntry(member)) {
return getMethodCodeLine(member, member.memberTags);
} else if (isGetterEntry(member)) {
function getCodeLine(member: MemberEntry): string {
if (isGetterEntry(member)) {
return getGetterCodeLine(member);
} else if (isSetterEntry(member)) {
return getSetterCodeLine(member);
@@ -281,11 +327,13 @@ function getPropertyCodeLine(member: PropertyEntry): string {
/** Map method entry to text */
function getMethodCodeLine(
member: FunctionEntry,
member: FunctionSignatureMetadata,
memberTags: MemberTags[] = [],
displayParamsInNewLines: boolean = false,
isFunction: boolean = false,
): string {
return `${memberTags.join(' ')} ${member.name}(${displayParamsInNewLines ? '\n ' : ''}${member.params
displayParamsInNewLines &&= member.params.length > 0;
return `${isFunction ? 'function' : ''}${memberTags.join(' ')} ${member.name}(${displayParamsInNewLines ? '\n ' : ''}${member.params
.map((param) => mapParamEntry(param))
.join(`,${displayParamsInNewLines ? '\n ' : ' '}`)}${
displayParamsInNewLines ? '\n' : ''
@@ -344,7 +392,7 @@ function getNumberOfLinesOfCode(contents: string): number {
/** Prints an initializer function signature into a single line. */
export function printInitializerFunctionSignatureLine(
name: string,
signature: FunctionEntry,
signature: FunctionSignatureMetadata,
showTypesInSignaturePreview: boolean,
): string {
let res = name;
@@ -381,7 +429,7 @@ export function printInitializerFunctionSignatureLine(
res += `: ${signature.returnType}`;
}
res += ';';
return res;
return `function ${res}`;
}
function appendPrefixAndSuffix(entry: DocEntry, codeTocData: CodeTableOfContentsData): void {
@@ -393,16 +441,63 @@ function appendPrefixAndSuffix(entry: DocEntry, codeTocData: CodeTableOfContents
data.contents = `${firstLine}\n${data.contents}${lastLine}`;
};
if (isClassEntry(entry)) {
const abstractPrefix = entry.isAbstract ? 'abstract ' : '';
appendFirstAndLastLines(codeTocData, `${abstractPrefix}class ${entry.name} {`, `}`);
if (isClassEntry(entry) || isInterfaceEntry(entry)) {
const generics =
entry.generics?.length > 0
? `<${entry.generics
.map((g) => (g.constraint ? `${g.name} extends ${g.constraint}` : g.name))
.join(', ')}>`
: '';
const extendsStr = entry.extends ? ` extends ${entry.extends}` : '';
// TODO: remove the ? when we distinguish Class & Decorator entries
const implementsStr =
entry.implements?.length > 0 ? ` implements ${entry.implements.join(' ,')}` : '';
const signature = `${entry.name}${generics}${extendsStr}${implementsStr}`;
if (isClassEntry(entry)) {
const abstractPrefix = entry.isAbstract ? 'abstract ' : '';
appendFirstAndLastLines(codeTocData, `${abstractPrefix}class ${signature} {`, `}`);
}
if (isInterfaceEntry(entry)) {
appendFirstAndLastLines(codeTocData, `interface ${signature} {`, `}`);
}
}
if (isEnumEntry(entry)) {
appendFirstAndLastLines(codeTocData, `enum ${entry.name} {`, `}`);
}
if (isInterfaceEntry(entry)) {
appendFirstAndLastLines(codeTocData, `interface ${entry.name} {`, `}`);
if (isDecoratorEntry(entry)) {
appendFirstAndLastLines(codeTocData, `interface ${entry.name} ({`, `})`);
}
}
/**
* Replaces any code block that isn't already wrapped by an anchor element
* by a link if the symbol is known
*/
export function addApiLinksToHtml(htmlString: string): string {
const result = htmlString.replace(
// This regex looks for span/code blocks not wrapped by an anchor block.
// Their content are then replaced with a link if the symbol is known
// The captured content ==> vvvvvvvv
/(?<!<a[^>]*>)(<(?:(?:span)|(?:code))[^>]*>\s*)([^<]*?)(\s*<\/(?:span|code)>)/g,
(type: string, span1: string, potentialSymbolName: string, span2: string) => {
let [symbol, subSymbol] = potentialSymbolName.split(/(?:#|\.)/) as [string, string?];
// mySymbol() => mySymbol
const symbolWithoutInvocation = symbol.replace(/\([^)]*\);?/g, '');
const moduleName = getModuleName(symbolWithoutInvocation)!;
if (moduleName) {
return `${span1}<a href="${getLinkToModule(moduleName, symbol, subSymbol)}">${potentialSymbolName}</a>${span2}`;
}
return type;
},
);
return result;
}
@@ -0,0 +1,38 @@
/*!
* @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.dev/license
*/
import {DecoratorEntry} from '../entities';
import {DecoratorEntryRenderable} from '../entities/renderables';
import {addRenderableCodeToc} from './code-transforms';
import {
addHtmlAdditionalLinks,
addHtmlDescription,
addHtmlJsDocTagComments,
addHtmlUsageNotes,
setEntryFlags,
} from './jsdoc-transforms';
import {addRenderableMembers} from './member-transforms';
import {addModuleName} from './module-name';
/** Given an unprocessed class entry, get the fully renderable class entry. */
export function getDecoratorRenderable(
classEntry: DecoratorEntry,
moduleName: string,
): DecoratorEntryRenderable {
return setEntryFlags(
addRenderableCodeToc(
addRenderableMembers(
addHtmlAdditionalLinks(
addHtmlUsageNotes(
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(classEntry, moduleName))),
),
),
),
),
) as DecoratorEntryRenderable;
}
@@ -6,9 +6,11 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {FunctionEntry, isFunctionEntryWithOverloads} from '../entities';
import {FunctionEntryRenderable} from '../entities/renderables';
import {HasRenderableOverloads} from '../entities/traits';
import {FunctionEntry, FunctionSignatureMetadata} from '../entities';
import {
FunctionEntryRenderable,
FunctionSignatureMetadataRenderable,
} from '../entities/renderables';
import {addRenderableCodeToc} from './code-transforms';
import {
addHtmlAdditionalLinks,
@@ -27,15 +29,10 @@ export function getFunctionRenderable(
): FunctionEntryRenderable {
return setEntryFlags(
addRenderableCodeToc(
addRenderableFunctionParams(
addOverloads(
moduleName,
addHtmlAdditionalLinks(
addHtmlUsageNotes(
setEntryFlags(
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(entry, moduleName))),
),
),
addHtmlAdditionalLinks(
addHtmlUsageNotes(
setEntryFlags(
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(entry, moduleName))),
),
),
),
@@ -43,15 +40,17 @@ export function getFunctionRenderable(
);
}
function addOverloads<T extends FunctionEntry>(
moduleName: string,
entry: T,
): T & HasRenderableOverloads {
return {
...entry,
overloads:
isFunctionEntryWithOverloads(entry) && entry.overloads
? entry.overloads.map((overload) => getFunctionRenderable(overload, moduleName))
: null,
};
export function getFunctionMetadataRenderable(
entry: FunctionSignatureMetadata,
moduleName: string = '',
): FunctionSignatureMetadataRenderable {
return addHtmlAdditionalLinks(
addRenderableFunctionParams(
addHtmlUsageNotes(
setEntryFlags(
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(entry, moduleName))),
),
),
),
);
}
@@ -16,7 +16,7 @@ import {
addHtmlUsageNotes,
setEntryFlags,
} from './jsdoc-transforms';
import {addRenderableGroupMembers} from './member-transforms';
import {addRenderableMembers} from './member-transforms';
import {addModuleName} from './module-name';
/** Given an unprocessed interface entry, get the fully renderable interface entry. */
@@ -26,7 +26,7 @@ export function getInterfaceRenderable(
): InterfaceEntryRenderable {
return setEntryFlags(
addRenderableCodeToc(
addRenderableGroupMembers(
addRenderableMembers(
addHtmlAdditionalLinks(
addHtmlUsageNotes(
addHtmlJsDocTagComments(addHtmlDescription(addModuleName(entry, moduleName))),
@@ -30,6 +30,8 @@ import {
} from '../entities/traits';
import {getLinkToModule} from './url-transforms';
import {addApiLinksToHtml} from './code-transforms';
import {getCurrentSymbol, getModuleName, logUnknownSymbol} from '../symbol-context';
export const JS_DOC_USAGE_NOTES_TAG = 'usageNotes';
export const JS_DOC_SEE_TAG = 'see';
@@ -37,6 +39,7 @@ export const JS_DOC_DESCRIPTION_TAG = 'description';
// Some links are written in the following format: {@link Route}
const jsDoclinkRegex = /\{\s*@link\s+([^}]+)\s*\}/;
const jsDoclinkRegexGlobal = new RegExp(jsDoclinkRegex.source, 'g');
/** Given an entity with a description, gets the entity augmented with an `htmlDescription`. */
export function addHtmlDescription<T extends HasDescription & HasModuleName>(
@@ -100,15 +103,18 @@ export function addHtmlUsageNotes<T extends HasJsDocTags>(entry: T): T & HasHtml
) as string)
: '';
const transformedHtml = addApiLinksToHtml(htmlUsageNotes);
return {
...entry,
htmlUsageNotes,
htmlUsageNotes: transformedHtml,
};
}
/** Given a markdown JsDoc text, gets the rendered HTML. */
function getHtmlForJsDocText<T extends HasModuleName>(text: string, entry: T): string {
return marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text), entry)) as string;
const parsed = marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text))) as string;
return addApiLinksToHtml(parsed);
}
export function setEntryFlags<T extends HasJsDocTags & HasModuleName>(
@@ -126,9 +132,7 @@ export function setEntryFlags<T extends HasJsDocTags & HasModuleName>(
};
}
function getHtmlAdditionalLinks<T extends HasJsDocTags & HasModuleName>(
entry: T,
): LinkEntryRenderable[] {
function getHtmlAdditionalLinks<T extends HasJsDocTags>(entry: T): LinkEntryRenderable[] {
const markdownLinkRule = /\[(.*?)\]\((.*?)(?: "(.*?)")?\)/;
const seeAlsoLinks = entry.jsdocTags
@@ -149,21 +153,8 @@ function getHtmlAdditionalLinks<T extends HasJsDocTags & HasModuleName>(
if (linkMatch) {
const link = linkMatch[1];
// handling links like {@link Route Some route with description}
const [symbol, description] = link.split(/\s(.+)/);
if (entry && description) {
return {
label: description.trim(),
url: `${getLinkToModule(entry.moduleName)}/${symbol}`,
};
}
// handling links like {@link Route}
return {
label: linkMatch[1].trim(),
url: `${getLinkToModule(entry.moduleName)}/${linkMatch[1].trim()}`,
};
const {url, label} = parseAtLink(link);
return {label, url};
}
return undefined;
@@ -195,15 +186,52 @@ function convertJsDocExampleToHtmlExample(text: string): string {
);
}
function convertLinks(text: string, entry: HasModuleName) {
return text.replace(jsDoclinkRegex, (_, link) => {
const [symbol, description] = link.split(/\s(.+)/);
if (symbol && description) {
// {@link Route Some route with description}
return `<a href="${getLinkToModule(entry.moduleName)}/${symbol}"><code>${description}</code></a>`;
} else {
// {@link Route}
return `<a href="${getLinkToModule(entry.moduleName)}/${symbol}"><code>${symbol}</code></a>`;
}
/**
* Converts {@link } tags into html anchor elements
*/
function convertLinks(text: string) {
return text.replace(jsDoclinkRegexGlobal, (_, link) => {
const {label, url} = parseAtLink(link);
return `<a href="${url}"><code>${label}</code></a>`;
});
}
function parseAtLink(link: string): {label: string; url: string} {
// Because of microsoft/TypeScript/issues/59679
// getTextOfJSDocComment introduces an extra space between the symbol and a trailing ()
link = link.replace(/ \(\)$/, '');
let [rawSymbol, description] = link.split(/\s(.+)/);
if (rawSymbol.startsWith('#')) {
rawSymbol = rawSymbol.substring(1);
} else if (rawSymbol.startsWith('http://') || rawSymbol.startsWith('https://')) {
return {
url: rawSymbol,
label: rawSymbol.split('/').pop()!,
};
}
let [symbol, subSymbol] = rawSymbol.replace(/\(\)$/, '').split(/(?:#|\.)/);
let moduleName = getModuleName(symbol);
const label = description ?? rawSymbol;
const currentSymbol = getCurrentSymbol();
if (!moduleName) {
// 2nd attemp, try to get the module name in the context of the current symbol
moduleName = getModuleName(`${currentSymbol}.${symbol}`);
if (!moduleName || !currentSymbol) {
// TODO: remove the links that generate this error
// TODO: throw an error when there are no more warning generated
logUnknownSymbol(link, symbol);
return {label, url: '#'};
}
subSymbol = symbol;
symbol = currentSymbol;
}
return {label, url: getLinkToModule(moduleName, symbol, subSymbol)};
}
@@ -8,14 +8,7 @@
import {MemberEntry, MemberTags, MemberType} from '../entities';
import {isClassMethodEntry} from '../entities/categorization';
import {MemberEntryRenderable} from '../entities/renderables';
import {
HasMembers,
HasModuleName,
HasRenderableMembers,
HasRenderableMembersGroups,
} from '../entities/traits';
import {HasMembers, HasModuleName, HasRenderableMembers} from '../entities/traits';
import {
addHtmlDescription,
@@ -69,44 +62,13 @@ export function mergeGettersAndSetters(members: MemberEntry[]): MemberEntry[] {
);
}
/** Given an entity with members, gets the entity augmented with renderable members. */
export function addRenderableGroupMembers<T extends HasMembers & HasModuleName>(
entry: T,
): T & HasRenderableMembersGroups {
const members = filterLifecycleMethods(entry.members);
const membersGroups = members.reduce((groups, item) => {
const member = setEntryFlags(
addMethodParamsDescription(
addHtmlDescription(
addHtmlUsageNotes(addHtmlJsDocTagComments(addModuleName(item, entry.moduleName))),
),
),
);
if (groups.has(member.name)) {
const group = groups.get(member.name);
group?.push(member);
} else {
groups.set(member.name, [member]);
}
return groups;
}, new Map<string, MemberEntryRenderable[]>());
return {
...entry,
membersGroups,
};
}
export function addRenderableMembers<T extends HasMembers & HasModuleName>(
entry: T,
): T & HasRenderableMembers {
const members = entry.members.map((member) =>
setEntryFlags(
addMethodParamsDescription(
addHtmlDescription(
addHtmlUsageNotes(addHtmlJsDocTagComments(addModuleName(member, entry.moduleName))),
),
addHtmlDescription(
addHtmlUsageNotes(addHtmlJsDocTagComments(addModuleName(member, entry.moduleName))),
),
),
);
@@ -116,15 +78,3 @@ export function addRenderableMembers<T extends HasMembers & HasModuleName>(
members,
};
}
function addMethodParamsDescription<T extends MemberEntry & HasModuleName>(entry: T): T {
if (isClassMethodEntry(entry)) {
return {
...entry,
params: entry.params.map((param) =>
addHtmlDescription(addModuleName(param, entry.moduleName)),
),
};
}
return entry;
}
@@ -3,18 +3,14 @@
* 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
* found in the LICENSE file at https://angular.dev/license
*/
export const API_PREFIX = 'api';
export const MODULE_NAME_PREFIX = '@angular/';
export function removeAngularPrefixFromModule(moduleName: string): string {
return moduleName.replace(MODULE_NAME_PREFIX, '');
}
export function getLinkToModule(moduleName: string) {
return `${API_PREFIX}/${removeAngularPrefixFromModule(moduleName)}`;
export function getLinkToModule(moduleName: string, symbol: string, subSymbol?: string) {
return `/${API_PREFIX}/${moduleName}/${symbol}${subSymbol ? `#${subSymbol}` : ''}`;
}
export const normalizePath = (path: string): string => {
@@ -18,8 +18,8 @@ export interface DocsCodeToken extends CodeToken {
// Capture group 1: all attributes on the opening tag
// Capture group 2: all content between the open and close tags
const singleFileSelfClosingCodeRule = /^\s*<docs-code\s([^>]*)((?:.(?!\/>))*)\/>/s;
const singleFileCodeRule = /^\s*<docs-code\s([^>]*)>((?:.(?!\/docs-code))*)<\/docs-code>/s;
const singleFileCodeRule =
/^\s*<docs-code((?:\s+[\w-]+(?:="[^"]*"|='[^']*'|=[^\s>]*)?)*)\s*(?:\/>|>(.*?)<\/docs-code>)/s;
const pathRule = /path="([^"]*)"/;
const headerRule = /header="([^"]*)"/;
@@ -38,10 +38,7 @@ export const docsCodeExtension = {
return src.match(/^<docs-code\s/)?.index;
},
tokenizer(this: TokenizerThis, src: string): DocsCodeToken | undefined {
const code = singleFileCodeRule.exec(src);
const selfClosingCode = singleFileSelfClosingCodeRule.exec(src);
const match = selfClosingCode ?? code;
const match = singleFileCodeRule.exec(src);
if (match) {
const attr = match[1].trim();
@@ -55,7 +52,7 @@ export const docsCodeExtension = {
const visibleRegion = visibleRegionRule.exec(attr);
const preview = previewRule.exec(attr) ? true : false;
let code = match[2].trim();
let code = match[2]?.trim() ?? '';
if (path && path[1]) {
code = loadWorkspaceRelativeFile(path[1]);
// Remove ESLint Comments
@@ -3,7 +3,7 @@
* 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
* found in the LICENSE file at https://angular.dev/license
*/
import {TokenizerThis, Tokens, RendererThis} from 'marked';
@@ -7,7 +7,7 @@
*/
import {Token, Tokens, RendererThis, TokenizerThis} from 'marked';
import {headingRender} from '../../tranformations/heading';
import {formatHeading, headingRender} from '../../tranformations/heading';
interface DocsStepToken extends Tokens.Generic {
type: 'docs-step';
@@ -51,7 +51,7 @@ export const docsStepExtension = {
return `
<li>
<span class="docs-step-number" aria-hidden="true"></span>
${headingRender(token.title, 3, token.title)}
${formatHeading({text: token.title, depth: 3})}
${this.parser.parse(token.tokens)}
</li>
`;
@@ -24,7 +24,7 @@ function getMermaidScriptTagData() {
}
return (mermaidScriptTagData = {
path: runfiles.resolveWorkspaceRelative('node_modules/mermaid/dist/mermaid.js'),
path: runfiles.resolve('npm/node_modules/mermaid/dist/mermaid.js'),
});
}
+2 -3
View File
@@ -8,7 +8,7 @@
import {marked} from 'marked';
import {hooks} from './hooks';
import {renderer} from './renderer';
import {Renderer} from './renderer';
import {docsAlertExtension} from './extensions/docs-alert';
import {docsCalloutExtension} from './extensions/docs-callout';
import {docsPillExtension} from './extensions/docs-pill/docs-pill';
@@ -33,7 +33,6 @@ export async function parseMarkdown(
marked.use({
hooks,
renderer,
extensions: [
docsAlertExtension,
docsCalloutExtension,
@@ -55,5 +54,5 @@ export async function parseMarkdown(
async: true,
});
return marked.parse(markdownContent);
return marked.parse(markdownContent, {renderer: new Renderer()});
}

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