Compare commits

..

63 Commits

Author SHA1 Message Date
Ben Hong b36d53143a docs: fix missing security guide in navigation
The paths for the security guide were flipped in the original PR. As a result, it looked for a markdown file in the best-practices directory when it should have looked for it in the guide directory instead.
2024-03-12 12:18:54 -07:00
Gerald Monaco 456f18be2e refactor(core): add internal API to enable i18n hydration (#54784)
Add an internal API to enable and use i18n hydration for testing and development. This helps ensure that we don't accidentally break the current behavior until we are completely ready to roll out i18n support.

PR Close #54784
2024-03-12 11:38:59 -07:00
cexbrayat db2f9a9561 refactor(compiler-cli): cleanup unused code (#54775)
The initializer api no longer needs to take care of `ɵoutput`.

PR Close #54775
2024-03-12 11:31:39 -07:00
Ben Hong 4ff94aa46c docs: improve in-depth guides information architecture (#54365)
PR Close #54365
2024-03-12 10:25:31 -07:00
Joey Perrott 6a07c0c2b2 fix(docs-infra): remove the prerender directory from adev (#54820)
Remove the prerender directory as it is entirely unused and unneeded

PR Close #54820
2024-03-12 10:24:04 -07:00
Paul Gschwendtner 1294b9a02a refactor(core): report subscription errors for OutputEmitterRef to ErrorHandler (#54821)
Currently if an `(output)` listener fails, it will be handled gracefully
by Angular and reported to the `ErrorHandler`.

For programmatic subscriptions with `OutputEmitterRef`, this is not the case.
Instead, as soon as any subscription is failing, all other subsequent
subscription callbacks are not firing anymore.

This commit intends to make this more consistent by gracefully
reporting errors from `OutputEmitterRef#emit` to `ErrorHandler`,
allowing for listener execution to continue.

PR Close #54821
2024-03-12 10:21:48 -07:00
Andrew Scott 8735af08b9 feat(router): Add ability to return UrlTree with NavigationBehaviorOptions from guards (#45023)
Returning `UrlTree` from a guard was a convenient new feature added to
the `Router`. However, it does not have feature-parity with the old
`router.navigate(...); return false;` pattern. The most common use-case
for this feature is to redirect to a new page _without_ updating the URL
from the initially attempted navigation. For example, rendering a 404
page when the user does not have access privelages to a route.

Fixes #17004
Fixes #27148

BREAKING CHANGE: Guards can now return `RedirectCommand` for redirects
in addition to `UrlTree`. Code which expects only `boolean` or `UrlTree`
values in `Route` types will need to be adjusted.

PR Close #45023
2024-03-12 09:19:14 -07:00
cexbrayat 87cae55e55 docs: typo in output documentation (#54773)
PR Close #54773
2024-03-12 09:15:04 -07:00
Angular Robot bea7f33ad7 docs: update Angular CLI help [main] (#54815)
Updated Angular CLI help contents.

PR Close #54815
2024-03-12 09:13:55 -07:00
Kristiyan Kostadinov 018f8266b3 fix(core): ensure all initializer functions run in an injection context (#54761)
Ensures that all of the functions intended to be run in initializers are in an injection context. This is a stop-gap until we have a compiler diagnostic for it.

PR Close #54761
2024-03-12 09:08:06 -07:00
Pawel Kozlowski 47f79e78c0 refactor(core): assert presence of the track function (#54814)
This commits assert that the repeater instruction gets a reference
to a tracking function. This change will allow us to better track
occurences of https://github.com/angular/angular/issues/53628 -
in certain situations a reference to a tracking function might be
undefiened.

We are not fixing the underlying issue here, just getting better
visibility.

PR Close #54814
2024-03-11 16:33:01 -07:00
Paul Gschwendtner 142825d7b1 perf(core): speed up retrieval of DestroyRef in EventEmitter (#54748)
Speeds up the retrieval of `DestroyRef` in `EventEmitter` because
`try/catch` is expensive if there is no injection context.

We saw a script time regression in Cloud.

The goldens had to be updated because `getInjectImplementation` is now
referenced. `inject` also references the underlying field, but directly.
This is super minimal overhead of a function exposing the internal
field.

PR Close #54748
2024-03-11 16:31:03 -07:00
Matthieu Riegler f0a26001c5 docs: use the right path for first-app images (#54694)
PR Close #54694
2024-03-11 16:30:28 -07:00
Kristiyan Kostadinov 6ea208ee90 refactor(compiler-cli): move defer resolver compilation into compiler package (#54759)
Moves the logic that creates the defer resolver function into `@angular/compiler` for consistency with the rest of the compilation APIs. Also renames some of the symbols to make it clearer what they're used for.

PR Close #54759
2024-03-11 15:52:42 -07:00
Kristiyan Kostadinov 9b424d7224 fix(compiler-cli): preserve original reference to non-deferrable dependency (#54759)
Fixes an issue where we were outputting the reference to non-deferrable dependencies as strings, rather than going through the reference emitter. This caused some issues internally because the reference wasn't maintained in the generated JS.

PR Close #54759
2024-03-11 15:52:41 -07:00
Kristiyan Kostadinov 83932aa85e refactor(compiler): rework defer block analysis (#54759)
Currently we have the `deferrableDeclToImportDecl`, `deferBlocks`, `deferrableTypes` and `deferBlockDepsEmitMode` fields on the `R3ComponentMetadata` which is incorrect, because the interface is used both for JIT and AOT mode even though the information for those fields is AOT-specific. It will be problematic for partial compilation since the runtime will have a reference to the dependency loading function, but will not be able to provide any of the other information.

These changes make the following refactors:
1. It changes the defer-related information in `R3ComponentMetadata` to include only references to dependency functions which can be provided both in JIT and AOT.
2. Moves the AOT-specific defer analysis into the `ComponentResolutionData`.
3. Moves the construction the defer dependency function into the compilation phase of the `ComponentDecoratorHandler`.
4. Drops support for defer blocks from the `TemplateDefinitionBuilder`. This allows us to clean up some TDB-specific code and shouldn't have an effect on users since the TDB isn't used anymore.

PR Close #54759
2024-03-11 15:52:41 -07:00
Andrew Scott 64f870c12b fix(core): ApplicationRef.tick should respect OnPush for host bindings (#53718) (#53718)
This commit updates `ApplicationRef.tick` to use `detectChangesInternal` for root
views rather than go through the `ChangeDetectorRef.detectChanges` API
which refreshes the host view without first looking at whether the view
is `OnPush` and not dirty. The current behavior would hide errors in
`OnPush` components that do not correctly get marked for check and would
break when migrating to zoneless change detection because `markForCheck`
was never called so change detection was never scheduled.
The error would be surprising and blamed on switching to zoneless when in
reality the issue already exists and is a problem with the component not
calling `markForCheck`. However, this error is hidden today because
`ApplicationRef.tick` refresh host bindings unconditionally.

BREAKING CHANGE: `OnPush` views at the root of the application need to
be marked dirty for their host bindings to refresh. Previously, the host
bindings were refreshed for all root views without respecting the
`OnPush` change detection strategy.

PR Close #53718

PR Close #53718
2024-03-11 13:46:11 -07:00
Joey Perrott d714e998d6 docs: bump to 2024 for copyright text (#54822)
Bump copyright text to 2024

PR Close #54822
2024-03-11 13:39:38 -07:00
Andrew Kushnir eaff724b77 fix(core): prevent infinite loops in clobbered elements check (#54425)
This commit updates HTML sanitization logic to avoid infinite loops in case clobbered elements contain fields like `nextSibling` or `parentNode`. Those fields are used for DOM traversal and this update makes sure that those calls return valid results.

Also this commit fixes an issue when clobbering `nodeName` causes JS exceptions.

PR Close #54425
2024-03-11 12:46:16 -07:00
Gerald Monaco 280a3a2d62 refactor(core): add i18nNodes in preparation for i18n hydration (#54750)
An i18n message effectively acts as a dynamic template: two elements with contiguous instruction indices won't necessarily be contiguous in the DOM.

For that reason, we need to maintain a mapping from instruction index to a physical DOM node in order to hydrate views with i18n, pointing to where hydration for that view should begin.

PR Close #54750
2024-03-11 11:12:43 -07:00
Matthieu Riegler 837016ccca build: remove AIO related extensions (#54698)
With AIO being deprecated those extensions can be removed.
PR Close #54698
2024-03-11 11:04:38 -07:00
Kristiyan Kostadinov 5ae2bf4806 fix(compiler): handle two-way bindings to signal-based template variables in instruction generation (#54714)
Updates the instruction generation for two-way bindings to only emit the `twoWayBindingSet` call when writing to template variables. Since template variables are constants, it's only allowed to write to them when they're signals. Non-signal values are flagged during template type checking.

Fixes #54670.

PR Close #54714
2024-03-11 11:01:43 -07:00
Kristiyan Kostadinov ffb9b44333 fix(compiler-cli): flag two-way bindings to non-signal values in templates (#54714)
We have a diagnostic that reports writes to template variables which worked both for regular event bindings and two-way bindings, however the latter was broken by #54154 because two-way bindings no longer had a `PropertyWrite` AST.

These changes fix the diagnostic and expand it to allow two-way bindings to template variables that are signals.

PR Close #54714
2024-03-11 11:01:42 -07:00
Kristiyan Kostadinov fd17b4e155 refactor(compiler-cli): move illegal template assignment check into template semantics checker (#54714)
Moves the check which ensures that there are no writes to template variables into the `TemplateSemanticsChecker` to prepare for the upcoming changes.

PR Close #54714
2024-03-11 11:01:42 -07:00
Kristiyan Kostadinov 6235095011 refactor(compiler-cli): move signal identification function (#54714)
Moves the function that identifies signals into a separate file so that it can be reused outside of extended diagnostics.

PR Close #54714
2024-03-11 11:01:42 -07:00
Kristiyan Kostadinov f86088f164 refactor(compiler-cli): introduce template semantics checker (#54714)
Introduces a new `TemplateSemanticsChecker` that will be used to flag semantic errors in the user's template. Currently we do some of this in the type check block, but the problem is that it doesn't have access to the template type checker which prevents us from properly checking cases like #54670. This pass is also distinct from the extended template checks, because we don't want users to be able to turn the checks off and we want them to run even if `strictTemplates` are disabled.

PR Close #54714
2024-03-11 11:01:42 -07:00
Matthieu Riegler 09a2f3d592 docs: adding signal inputs and model to ADEV. (#54753)
Those docs are a copy from AIO.

PR Close #54753
2024-03-11 11:00:49 -07:00
theRealc0d3c 55c647bf90 docs: fix typo in tutorial (#54817)
PR Close #54817
2024-03-11 10:59:49 -07:00
Matthieu Riegler 1c9abb9ac8 build: Unplug AIO from CI (#54765)
The time has come to unplug AIO from the CI.

Farewell and thanks for your services 🫡

PR Close #54765
2024-03-11 10:59:07 -07:00
Kristiyan Kostadinov 81ccf5d102 fix(compiler): not catching for loop empty tracking expressions (#54772)
Fixes that the template parser wasn't catching empty expressions in the `track` parameter of for loops.

Fixes #54763.

PR Close #54772
2024-03-11 09:17:39 -07:00
Kristiyan Kostadinov 5b927c094d build: update to TypeScript 5.4 stable (#54743)
Updates the repo to the stable version of TypeScript 5.4.

PR Close #54743
2024-03-11 09:16:55 -07:00
JoostK d269c88ae4 refactor(core): avoid additional closure for queued microtask callback (#54801)
The `runCallbackOnce` closure is declared not to have any parameters itself, so it is
compatible as `queueMicrotask` callback without the extra closure. This reduces the call
stack by a frame and avoids the extra closure allocation.

PR Close #54801
2024-03-11 08:59:55 -07:00
JoostK fa315d4dad refactor(core): properly type withBody and withHead testing helpers (#54801)
This commit addresses a typing mismatch, where these functions were declared to return whichever
value their callback returned, but this was inaccurate: it's always a test callback function
with `done` argument.

PR Close #54801
2024-03-11 08:59:55 -07:00
JoostK 0bcaa0ebee test(core): ensure async tests are awaited properly (#54801)
The assertion in `packages/core/test/acceptance/after_render_hook_spec.ts:165` was prone to flakes,
where Jasmine could frequently report an error:

```
Error: 'expect' was used when there was no current spec, this could be because an asynchronous test timed out
    at Env.expect (node_modules/jasmine-core/lib/jasmine-core/jasmine.js:1945:15)
    at expect (node_modules/jasmine-core/lib/jasmine-core/jasmine.js:8267:18)
    at file:///packages/core/test/acceptance/after_render_hook_spec.ts:165:12
```

This happens because `wrapTestFn` checks for an exact type of `Promise`, which may have been patched by zone.js
such that the `instanceof` condition is dependent on whether zone.js has patched the `Promise` constructor.

PR Close #54801
2024-03-11 08:59:55 -07:00
Matthieu Riegler 484ae23bbf build: remove non-exisiting target from test:ci (#54791)
This target doesn't exist any more.

PR Close #54791
2024-03-11 14:06:50 +00:00
Dylan Hunn 54340a9fff refactor(compiler): Delete TemplateDefinitionBuilder acceptance test expectations (#54757)
Many acceptance tests define goldens for both `TemplateDefinitionBuilder` and Template Pipeline. All such tests have had the TDB golden files removed, and the corresponding expectations adjusted.

PR Close #54757
2024-03-08 16:51:01 -08:00
Dylan Hunn ef32b5322e refactor(compiler): Delete TemplateDefinitionBuilder and helpers (#54757)
`TemplateDefinitionBuilder` is the legacy template compiler, and was replaced by Template Pipeline as the default in v17.3.

This PR attempts to delete `TemplateDefinitionBuilder`, `ExpressionConverter`, and various helpers (i18n context, style builder, property visitors, etc).

Consider this a first pass: a lot of code has not yet been deleted (e.g. old TDB-specific test cases), and I'm sure I have missed additional helper code.

PR Close #54757
2024-03-08 16:51:01 -08:00
Andrew Scott 37d1f713a4 Revert "fix(core): ApplicationRef.tick should respect OnPush for host bindings (#53718)" (#54774)
This reverts commit d888da4606.
2024-03-08 05:57:15 -08:00
Andrew Scott fb189c6997 Revert "refactor(compiler): rework defer block analysis (#54700)" (#54758)
This reverts commit eee620aa00.
2024-03-07 16:20:09 -08:00
Kristiyan Kostadinov 8180c39479 build: fix size failure (#54756)
Fixes a broken size CI check.

PR Close #54756
2024-03-07 16:13:43 -08:00
Andrew Scott 115ee88ba9 fix(router): Navigations triggered by cancellation events should cancel previous navigation (#54710)
There is an edge case where synchronous navigations caused in
response to navigation events can result in a previous navigation not
being unsubscribed from. b/328219996

PR Close #54710
2024-03-07 13:07:08 -08:00
Andrew Scott 2787c50107 refactor(core): render hooks should always run on node attach or detach (#54083)
This commit ensures that render hooks are rerun when a node is attached
or detached. We do not necessarily need to run change detection but DOM
did change so render hooks should execute.

PR Close #54083
2024-03-07 12:59:10 -08:00
Andrew Scott d4247af8c5 refactor(core): Skip refresh views if render hooks are the only notification source (#54083)
Do not refresh views if the only thing that notified the scheduler was
registration of a new render hook.

PR Close #54083
2024-03-07 12:59:10 -08:00
Andrew Scott 53e743938f refactor(core): registering afterRender hooks notify scheduler (#54083)
This commit updates the `afterRender` and `afterNextRender` hooks to
notify the scheduler (which subsequently schedules change detection)
when created. This makes the hooks similar to `requestAnimationFrame`,
which requests that the browser schedule a rendering operation. This
reqeust is not conditional. Even if there was nothing to repaint, the
`requestAnimationFrame` callback will execute.

In Angular, this is useful because callers of `afterNextRender` don't
necessarily have any way of knowing whether a change detection is even
scheduled. For example, the anchor scrolling with the Angular Router
needs to wait for rendering to complete before attempting to scroll
because rendering can affect the size of the page. However, if the user
is already on the page that the navigation is targeting, such as
navigating to an anchor on the page, there is nothing new for the Router
to render so a render might not even be scheduled.

Related to https://github.com/angular/angular/issues/53985, which
could use `afterNextRender` instead of `setTimeout` to ensure the
scrolling happens in the same frame as the page rendering, but would not
necessarily work without this change (as described above). Note that the
scrolling _cannot_ use a microtask to ensure scrolling happens in the
same frame because `NgZone` will ensure microtasks flush before
change detection, so it would cause the scroll to happen before rendering.

PR Close #54083
2024-03-07 12:59:09 -08:00
Gerald Monaco 43ab781b70 refactor(core): refactor i18n node creation to prepare for hydration (#54722)
Adds a `locateOrCreateNode` helper and uses it for all DOM operations in preparation for supporting hydration.

PR Close #54722
2024-03-07 12:41:43 -08:00
Kristiyan Kostadinov eee620aa00 refactor(compiler): rework defer block analysis (#54700)
Currently we have the `deferrableDeclToImportDecl`, `deferBlocks`, `deferrableTypes` and `deferBlockDepsEmitMode` fields on the `R3ComponentMetadata` which is incorrect, because the interface is used both for JIT and AOT mode even though the information for those fields is AOT-specific. It will be problematic for partial compilation since the runtime will have a reference to the dependency loading function, but will not be able to provide any of the other information.

These changes make the following refactors:
1. It changes the defer-related information in `R3ComponentMetadata` to include only references to dependency functions which can be provided both in JIT and AOT.
2. Moves the AOT-specific defer analysis into the `ComponentResolutionData`.
3. Moves the construction the defer dependency function into the compilation phase of the `ComponentDecoratorHandler`.
4. Drops support for defer blocks from the `TemplateDefinitionBuilder`. This allows us to clean up some TDB-specific code and shouldn't have an effect on users since the TDB isn't used anymore.

PR Close #54700
2024-03-07 12:40:44 -08:00
Andrew Scott d888da4606 fix(core): ApplicationRef.tick should respect OnPush for host bindings (#53718)
This commit updates `ApplicationRef.tick` to use `detectChangesInternal` for root
views rather than go through the `ChangeDetectorRef.detectChanges` API
which refreshes the host view without first looking at whether the view
is `OnPush` and not dirty. The current behavior would hide errors in
`OnPush` components that do not correctly get marked for check and would
break when migrating to zoneless change detection because `markForCheck`
was never called so change detection was never scheduled.
The error would be surprising and blamed on switching to zoneless when in
reality the issue already exists and is a problem with the component not
calling `markForCheck`. However, this error is hidden today because
`ApplicationRef.tick` refresh host bindings unconditionally.

BREAKING CHANGE: `OnPush` views at the root of the application need to
be marked dirty for their host bindings to refresh. Previously, the host
bindings were refreshed for all root views without respecting the
`OnPush` change detection strategy.

PR Close #53718
2024-03-07 11:13:03 -08:00
Leonardo Ortiz 8bf842d595 docs: update content readme link (#53763)
PR Close #53763
2024-03-07 11:06:55 -08:00
Leonardo Ortiz 30dccd4b77 docs: readme from blossom (#53763)
PR Close #53763
2024-03-07 11:06:55 -08:00
Angular Robot 4f808ad353 docs: update events (#54721)
Generated `events.json` with the latest events retrieved from the Firebase DB.

PR Close #54721
2024-03-07 11:05:36 -08:00
Gerald Monaco e0c1f1e183 refactor(core): don't hydrate detached nodes (#54723)
In preparation for hydration support, make sure that we don't attempt to hydrate detached nodes and instead, always newly create them.

PR Close #54723
2024-03-07 11:01:29 -08:00
Gerald Monaco 9104c62ab3 refactor(core): add i18n AST to prepare for hydration (#54724)
In order to serialize and hydrate i18n blocks, we need to be able to walk an AST for the translated message. This AST is generated during normal parsing of the message.

PR Close #54724
2024-03-07 11:00:36 -08:00
Alan Agius 2258ac7a32 fix(http): exclude caching for authenticated HTTP requests (#54746)
This update modifies the transfer cache logic to prevent caching of HTTP requests that require authorization.

Closes: #54745

PR Close #54746
2024-03-07 11:00:00 -08:00
Dylan Hunn ef39107ce5 refactor(language-service): Replace tsserverlibrary -> typescript (#54726)
Typescript recently consolidated `tsserverlibrary` into `typescript`: [blog post](https://devblogs.microsoft.com/typescript/announcing-typescript-5-3/#consolidation-between-tsserverlibrary-js-and-typescript-js)

In this commit, we remove all references to `tsserverlibrary` accordingly. This should be safe, since v18 and later support TS 5.3+.

PR Close #54726
2024-03-07 10:49:01 -08:00
Dylan Hunn 126879e9d0 refactor(language-service): Make a variety of fixes to the language service to build in g3. (#54726)
The following changes help the language service code build in g3:
* `Omit<T>` produces an index signature, so we must access the resulting properties with square bracket (because `noPropertyAccessFromIndexSignature` is on in g3).
* Explicitly export `absoluteFrom` from `packages/compiler-cli/index.ts`, since the `*` re-export is patched out in g3.
* Remove const from a few const enums, since accessing const enums across modules is not compatible with `isolatedModules` (which is on in g3).

PR Close #54726
2024-03-07 10:49:01 -08:00
Andrew Scott cd242a11f5 refactor(core): Remove isInternal flag (#54740)
This is not needed any longer since the changes that used it have
landed.

PR Close #54740
2024-03-07 09:07:01 -08:00
Andrew Scott ad045efd4b fix(core): Ensure views marked for check are refreshed during change detection (#54735)
When a view has the `Dirty` flag and is reattached, we should ensure that it is
reached and refreshed during the next change detection run from above.

In addition, when a view is created and attached, we should ensure that it is reached
and refreshed during change detection. This can happen if the view is
created and attached outside a change run or when it is created and
attached after its insertion view was already checked. In both cases, we
should ensure that the view is reached and refreshed during either the
current change detection or the next one (if change detection is not
already running).

We can achieve this by creating all views with the `Dirty` flag set.

However, this does happen to be a breaking change in some scenarios.
The one identified internally was actually depending on change detection
_not_ running immediately because it relied on an input value that was
set using `ngModel`. Because `ngModel` sets its value in a `Promise`, it
is not available until the _next_ change detection cycle. Ensuring
created views run in the current change change detection will result in
different behavior in this case.

fixes #52928
fixes #15634

BREAKING CHANGE: Newly created and views marked for check and reattached
during change detection are now guaranteed to be refreshed in that same
change detection cycle. Previously, if they were attached at a location
in the view tree that was already checked, they would either throw
`ExpressionChangedAfterItHasBeenCheckedError` or not be refreshed until
some future round of change detection. In rare circumstances, this
correction can cause issues. We identified one instance that relied on
the previous behavior by reading a value on initialization which was
queued to be updated in a microtask instead of being available in the
current change detection round. The component only read this value during
initialization and did not read it again after the microtask updated it.

PR Close #54735
2024-03-06 15:44:09 -08:00
Andrew Scott ba8e465974 fix(core): Change Detection will continue to refresh views while marked for check (#54734)
When the `ApplicationRef` refreshes attached views, it will continue to
do so while there is still one marked for check after the refresh
completes.

BREAKING CHANGE: When Angular runs change detection, it will continue to
refresh any views attached to `ApplicationRef` that are still marked for
check after one round completes. In rare cases, this can result in infinite
loops when certain patterns continue to mark views for check using
`ChangeDetectorRef.detectChanges`. This will be surfaced as a runtime
error with the `NG0103` code.

PR Close #54734
2024-03-06 15:43:16 -08:00
Andrew Scott d9c0a16521 refactor(core): Fix internal test util types (#54736)
This fixes and error that has been seen recently where compilation fails
due to domino.impl type not being recognized

PR Close #54736
2024-03-06 13:34:53 -08:00
Andrew Scott 24bc0ed4f2 fix(core): ComponentFixture autodetect should detect changes within ApplicationRef.tick (#54733)
The current behavior of `autoDetect` in `ComponentFixture` does not
match production very well. It has several flaws that make it an
insufficient change detection mechanism:

* It runs change detection for the component under test _after_ views
  attached to the `ApplicationRef`. This can cause real behavior
  differences that break in production, because tests can observe view
  refreshes in the incorrect order (for example, a dialog refreshing
  before the component which opened it).
* Because of the above ordering, render hooks registered during change
  detection of the fixture views _will not execute at all_ because
  `ApplicationRef.tick` already happen.
* It does not rerun change detection on the view tree if there are more
  dirty views to refresh after the render hooks complete.
* It effectively hides/swallows errors from change detection inside the
  `onMicrotaskEmpty` subscription by not reporting them to the error
  handler. Instead, this error ends up being unhandled in the
  subscription and rxjs throws these in a `setTimeout`.

All of the above are problematic but this commit _does not_ fix the
final point. Ideally, we can land this in a future change but this
requires additional internal fixes. In the meantime, we have to juggle
special-case handling of the component fixture views within
`ApplicationRef.tick` using some special events to retain current
behavior and avoid errors from the fixture propagating to the `ErrorHandler`.

BREAKING CHANGE: The `ComponentFixture.autoDetect` feature now executes
change detection for the fixture within `ApplicationRef.tick`. This more
closely matches the behavior of how a component would refresh in
production. The order of component refresh in tests may be slightly
affected as a result, especially when dealing with additional components
attached to the application, such as dialogs. Tests sensitive to this
type of change (such as screenshot tests) may need to be updated.
Concretely, this change means that the component will refresh _before_
additional views attached to `ApplicationRef` (i.e. dialog components).
Prior to this change, the fixture component would refresh _after_ other
views attached to the application.

PR Close #54733
2024-03-06 13:33:54 -08:00
Andrew Scott fc0f47c53f release: bump the next branch to v18.0.0-next.0 (#54737) 2024-03-06 13:26:34 -08:00
Pawel Kozlowski 781dbe2cc2 docs: release notes for the v17.3.0-rc.0 release 2024-03-06 20:44:52 +01:00
Pawel Kozlowski 24a35366ca release: bump the next branch to v17.4.0-next.0 2024-03-06 20:44:52 +01:00
509 changed files with 8439 additions and 21329 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ description: 'Installs the dependencies using Yarn'
runs:
using: 'composite'
steps:
- uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4
- uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
with:
path: |
./node_modules/
+4 -4
View File
@@ -21,16 +21,16 @@ jobs:
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview'))
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@5774b71c01a55c4c998f858ee37d3b77ae704c31
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@5774b71c01a55c4c998f858ee37d3b77ae704c31
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@5774b71c01a55c4c998f858ee37d3b77ae704c31
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel build --config=aio_local_deps //adev:build
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@5774b71c01a55c4c998f858ee37d3b77ae704c31
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
with:
workflow-artifact-name: 'adev-preview'
pull-number: '${{github.event.pull_request.number}}'
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs
npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}}
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@5774b71c01a55c4c998f858ee37d3b77ae704c31
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'adev-preview'
-42
View File
@@ -1,42 +0,0 @@
# This workflow builds the AIO previews for pull requests when a certain label is applied.
# The actual deployment happens as part of a dedicated second workflow to avoid security
# issues where the building would otherwise occur in an authorized context where secrets
# could be leaked. More details can be found here:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
name: Build AIO app for preview deployment
on:
pull_request:
types: [synchronize, labeled]
permissions: read-all
jobs:
aio-build:
runs-on: ubuntu-latest
# We only want to build and deploy the AIO app if the `aio: preview` label has been
# added, or if the label is already applied and new changes have been made in the PR.
if: |
(github.event.action == 'labeled' && github.event.label.name == 'aio: preview') ||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'aio: preview'))
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: ./.github/actions/yarn-install
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
with:
bazelrc: ./.bazelrc.user
# Build the web package. Note: We run Bazel from a low-resource Github action container,
# so we manually need to instruct Bazel to run more actions concurrently as by default
# the number of concurrent actions is determined based on the host resources.
- run: bazel build //aio:build --jobs=32 --announce_rc --verbose_failures
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c83e99a12397014162531ca125c94549db55dd84
with:
workflow-artifact-name: 'aio'
pull-number: '${{github.event.pull_request.number}}'
artifact-build-revision: '${{github.event.pull_request.head.sha}}'
deploy-directory: './dist/bin/aio/build'
-44
View File
@@ -1,44 +0,0 @@
# This workflow runs whenever the AIO build workflow has completed. Deployment happens
# as part of a dedicated second workflow to avoid security issues where the building would
# otherwise occur in an authorized context where secrets could be leaked.
#
# More details can be found here:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
name: Deploying AIO preview to Firebase
on:
workflow_run:
workflows: ['Build AIO app for preview deployment']
types: [completed]
permissions:
# Needed in order to be able to comment on the pull request.
pull-requests: write
env:
PREVIEW_PROJECT: ng-dev-previews
PREVIEW_SITE: ng-dev-previews-fw
jobs:
aio-deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Configure Firebase deploy target
working-directory: aio/
run: |
# We can use `npx` as the Firebase deploy actions uses it too.
npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting aio
npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting aio ${{env.PREVIEW_SITE}}
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c83e99a12397014162531ca125c94549db55dd84
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'aio'
firebase-config-dir: './aio/'
firebase-public-dir: './aio/dist'
firebase-project-id: '${{env.PREVIEW_PROJECT}}'
firebase-service-key: '${{secrets.FIREBASE_PREVIEW_SERVICE_TOKEN}}'
@@ -16,6 +16,6 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@4b433074a806bbbd4d319264430740cd46e62f27
- uses: angular/dev-infra/github-actions/branch-manager@c83e99a12397014162531ca125c94549db55dd84
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+3 -3
View File
@@ -19,7 +19,7 @@ jobs:
exit: true
# Indicate that the benchmark command was received.
- uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
- uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3
with:
comment-id: ${{github.event.comment.id}}
token: '${{secrets.BENCHMARK_POST_RESULTS_GITHUB_TOKEN}}'
@@ -38,7 +38,7 @@ jobs:
- uses: ./.github/actions/yarn-install
- uses: angular/dev-infra/github-actions/bazel/configure-remote@5774b71c01a55c4c998f858ee37d3b77ae704c31
- uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
with:
bazelrc: ./.bazelrc.user
@@ -53,7 +53,7 @@ jobs:
id: benchmark
name: Running benchmark
- uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
- uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3
with:
issue-number: ${{github.event.issue.number}}
token: '${{secrets.BENCHMARK_POST_RESULTS_GITHUB_TOKEN}}'
+64
View File
@@ -0,0 +1,64 @@
name: CI
on:
pull_request_target:
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
permissions: {}
defaults:
run:
shell: bash
jobs:
saucelabs:
runs-on: ubuntu-latest-4core
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
SAUCE_USERNAME: ${{ vars.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
# Checking out the pull request commit is intended here as we need to run the changed code tests.
ref: ${{ github.event.pull_request.head.sha }}
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Starting Saucelabs tunnel service
run: ./tools/saucelabs/sauce-service.sh run &
# Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling
# 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
+24 -158
View File
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
@@ -35,8 +35,6 @@ jobs:
run: yarn install --frozen-lockfile --cwd aio
- name: Check code lint
run: yarn -s tslint
- name: Check code lint in aio
run: yarn -s --cwd aio lint
- name: Check for circular dependencies
run: yarn -s ts-circular-deps:check
- name: Validate pull approve configuration
@@ -60,13 +58,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Run unit tests
@@ -78,132 +76,46 @@ jobs:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile --network-timeout 100000
- name: Run CI tests for framework
run: yarn test:ci
aio:
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules for aio
run: yarn install --cwd aio --frozen-lockfile
- name: Run AIO tests with upstream packages
run: yarn --cwd aio test:ci
- name: Check generated bundle sizes
run: yarn --cwd aio payload-size
adev:
runs-on:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
run: yarn bazel test --config=aio_local_deps //adev:test
aio-local:
runs-on:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --cwd aio --frozen-lockfile
- name: Run AIO tests with local packages
run: yarn --cwd aio test-local:ci
- name: Check generated bundle sizes
run: yarn --cwd aio payload-size aio-local
aio-deploy:
needs: [aio]
if: needs.aio.result == 'success' && github.event_name == 'push'
runs-on:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules for aio
run: yarn install --cwd aio --frozen-lockfile
- name: Set the stable branch environment variable
id: latest_version
run: |
echo "CI_STABLE_BRANCH=$(npm info @angular/core dist-tags.latest | sed -r 's/^\s*([0-9]+\.[0-9]+)\.[0-9]+.*$/\1.x/')" >> $GITHUB_OUTPUT
echo "CI_BRANCH=$(echo ${{ github.event.ref }} | cut -d '/' -f 3)" >> $GITHUB_OUTPUT
- name: Deploy aio to production
env:
CI_AIO_MIN_PWA_SCORE: '95'
CI_COMMIT: ${{ github.event.after }}
CI_REPO_NAME: 'angular'
CI_PULL_REQUEST: 'false'
CI_REPO_OWNER: 'angular'
CI_BUILD_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
CI_STABLE_BRANCH: ${{ steps.latest_version.outputs.CI_STABLE_BRANCH }}
CI_BRANCH: ${{ steps.latest_version.outputs.CI_BRANCH }}
CI_SECRET_AIO_DEPLOY_FIREBASE_TOKEN: ${{ secrets.AIO_DEPLOY_TOKEN }}
CI_SECRET_PAYLOAD_FIREBASE_TOKEN: ${{ secrets.AIO_PAYLOAD_TOKEN }}
run: yarn --cwd aio deploy-production
- name: Notify about failed deployment
if: ${{ failure() }}
uses: slackapi/slack-github-action@2a8087d4af6f83146a87539a70defe909fe6dbe6 # v=v1.24.0
with:
channel-id: 'C07DT5M6V,CKV1F72BG'
slack-message: 'Deploy to aio job failed for ${{ github.base_ref }} branch failed on build ${{ github.event.after }}: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
env:
SLACK_BOT_TOKEN: ${{ secrets.ANGULAR_ROBOT_SLACK_TOKEN }}
publish-snapshots:
if: github.event_name == 'push'
runs-on:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -215,7 +127,7 @@ jobs:
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
@@ -223,9 +135,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@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- run: |
@@ -264,7 +176,7 @@ jobs:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
# Checking out the pull request commit is intended here as we need to run the changed code tests.
@@ -272,66 +184,20 @@ jobs:
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@4b433074a806bbbd4d319264430740cd46e62f27
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@50bb71e51d3372ca2e359adb9069f89beeac4ae7
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Set up Sauce Tunnel Daemon
run: yarn bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
SAUCE_USERNAME: ${{ vars.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
- 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}
saucelabs:
runs-on: ubuntu-latest-4core
env:
SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
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@4b433074a806bbbd4d319264430740cd46e62f27
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@50bb71e51d3372ca2e359adb9069f89beeac4ae7
- 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
adev-deploy:
needs: [adev]
if: needs.adev.result == 'success' && github.event_name == 'push' && github.ref_name == 'main'
@@ -341,11 +207,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@4b433074a806bbbd4d319264430740cd46e62f27
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules
run: yarn install --frozen-lockfile
- name: Build adev to ensure it continues to work
+2 -2
View File
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/commit-message-based-labels@4b433074a806bbbd4d319264430740cd46e62f27
- uses: angular/dev-infra/github-actions/commit-message-based-labels@c83e99a12397014162531ca125c94549db55dd84
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/post-approval-changes@4b433074a806bbbd4d319264430740cd46e62f27
- uses: angular/dev-infra/github-actions/post-approval-changes@c83e99a12397014162531ca125c94549db55dd84
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: angular/dev-infra/github-actions/google-internal-tests@4b433074a806bbbd4d319264430740cd46e62f27
- uses: angular/dev-infra/github-actions/google-internal-tests@c83e99a12397014162531ca125c94549db55dd84
with:
run-tests-guide-url: http://go/angular-g3sync-start
github-token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
status:
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/unified-status-check@4b433074a806bbbd4d319264430740cd46e62f27
- uses: angular/dev-infra/github-actions/unified-status-check@c83e99a12397014162531ca125c94549db55dd84
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+4 -4
View File
@@ -22,15 +22,15 @@ jobs:
version: ['https://next.angular.io/', 'https://angular.io/']
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@5774b71c01a55c4c998f858ee37d3b77ae704c31
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
./aio/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@5774b71c01a55c4c998f858ee37d3b77ae704c31
uses: angular/dev-infra/github-actions/bazel/setup@c83e99a12397014162531ca125c94549db55dd84
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@5774b71c01a55c4c998f858ee37d3b77ae704c31
uses: angular/dev-infra/github-actions/bazel/configure-remote@c83e99a12397014162531ca125c94549db55dd84
- name: Install node modules in aio
run: yarn install --frozen-lockfile --cwd aio
- name: Run basic e2e and deployment config tests.
@@ -44,7 +44,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@5774b71c01a55c4c998f858ee37d3b77ae704c31
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c83e99a12397014162531ca125c94549db55dd84
with:
cache-node-modules: true
node-module-directories: |
+1 -1
View File
@@ -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@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10
uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
with:
sarif_file: results.sarif
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
env:
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
- name: Create a PR (if necessary)
uses: angular/dev-infra/github-actions/create-pr-for-changes@1421ff04811ecb45812930c5d1f61d912880e75f
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
with:
branch-prefix: update-cli-help
pr-title: 'docs: update Angular CLI help [${{github.ref_name}}]'
-48
View File
@@ -1,48 +0,0 @@
# Update the `events.json` file that powers the [AIO events page](https://angular.io/events) (if
# necessary) and create a pull request.
#
# For more details on the overall process, see
# [aio/scripts/generate-events/README.md](../../aio/scripts/generate-events/README.md).
name: Update AIO events
on:
workflow_dispatch:
inputs: {}
schedule:
# Run every day at 15:00.
- cron: '0 15 * * *'
# Declare default permissions as read only.
permissions:
contents: read
jobs:
update_events:
name: Update `events.json` (if necessary)
if: github.repository == 'angular/angular'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
# Setting `persist-credentials: false` prevents the github-action account from being the
# account that is attempted to be used for authentication, instead the remote is set to
# an authenticated URL.
persist-credentials: false
# This is needed as otherwise the PR creation will fail with `shallow update not allowed` when the forked branch is not in sync.
fetch-depth: 0
- name: Generate `events.json`
run: node aio/scripts/generate-events/index.mjs --ignore-invalid-dates
- name: Create a PR (if necessary)
uses: angular/dev-infra/github-actions/create-pr-for-changes@c83e99a12397014162531ca125c94549db55dd84
with:
branch-prefix: docs-update-events
pr-title: 'docs: update events'
pr-description: |
Generated `events.json` with the latest events retrieved from the Firebase DB.
pr-labels: |
action: review
area: docs
target: patch
angular-robot-token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
+22 -83
View File
@@ -140,7 +140,7 @@ groups:
- alxhub
- crisbeto
- ~jelbourn
- thePunderWoman
- jessicajaniuk
# =========================================================
# Framework: Compiler
@@ -183,7 +183,7 @@ groups:
- crisbeto
- devversion
- dylhunn
- thePunderWoman
- jessicajaniuk
- AndrewKushnir
# =========================================================
@@ -193,7 +193,7 @@ groups:
<<: *defaults
conditions:
- >
contains_any_globs(files.exclude("packages/core/schematics/*").exclude('packages/core/primitives/*'), [
contains_any_globs(files.exclude("packages/core/schematics/*"), [
'packages/core/**/{*,.*}',
'packages/examples/core/**/{*,.*}',
'packages/platform-browser/**/{*,.*}',
@@ -358,7 +358,7 @@ groups:
- crisbeto
- devversion
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -378,7 +378,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -415,7 +415,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -438,7 +438,7 @@ groups:
- andrewseguin
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -536,7 +536,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -615,7 +615,7 @@ groups:
reviewers:
users:
- alxhub
- thePunderWoman
- jessicajaniuk
# =========================================================
# Framework: Testing
@@ -644,7 +644,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -663,7 +663,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -682,7 +682,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -778,7 +778,7 @@ groups:
- atscott
- crisbeto
- dylhunn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
# =========================================================
@@ -813,7 +813,7 @@ groups:
- AndrewKushnir
- atscott
- dylhunn
- thePunderWoman
- jessicajaniuk
- josephperrott
- pkozlowski-opensource
@@ -867,7 +867,7 @@ groups:
reviewers:
users:
- AndrewKushnir
- thePunderWoman
- jessicajaniuk
# =========================================================
# Docs: Marketing
@@ -946,7 +946,7 @@ groups:
- alxhub
- AndrewKushnir
- atscott
- thePunderWoman
- jessicajaniuk
- jelbourn
# =========================================================
@@ -1078,12 +1078,11 @@ groups:
- josephperrott
- jelbourn
- bencodezen
- thePunderWoman
- jessicajaniuk
- dylhunn
- AndrewKushnir
- alxhub
- crisbeto
- devversion
- atscott
- pkozlowski-opensource
- ~JeanMeche
@@ -1144,7 +1143,7 @@ groups:
reviewers:
users:
- AndrewKushnir
- thePunderWoman
- jessicajaniuk
- josephperrott
# =========================================================
@@ -1243,7 +1242,7 @@ groups:
- atscott
- dylhunn
- ~jelbourn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
reviews:
request: 3 # Request reviews from 3 people
@@ -1269,7 +1268,7 @@ groups:
- atscott
- dylhunn
- ~jelbourn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
reviews:
request: 2 # Request reviews from 2 people
@@ -1295,7 +1294,7 @@ groups:
- atscott
- dylhunn
- ~jelbourn
- thePunderWoman
- jessicajaniuk
- pkozlowski-opensource
####################################################################################
@@ -1319,69 +1318,9 @@ groups:
- andrewseguin
- dgp1130
- ~jelbourn
- thePunderWoman
- jessicajaniuk
- josephperrott
# =========================================================
# Primitives
# =========================================================
# Angular team required reviews
primitives:
<<: *defaults
conditions:
- >
contains_any_globs(files, [
'packages/core/primitives/**/{*,.*}',
])
reviewers:
users:
- pkozlowski-opensource # Pawel Kozlowski
- alxhub # Alex Rickabaugh
- thePunderWoman # Jessica Janiuk
- dylhunn # Dylan Hunn
- AndrewKushnir # Andrew Kushnir
- atscott # Andrew Scott
reviews:
required: 1
reviewed_for: required
labels:
pending: 'requires: TGP'
approved: 'requires: TGP'
rejected: 'requires: TGP'
# External team required reviews
primitives-shared:
<<: *defaults
conditions:
- >
contains_any_globs(files, [
'packages/core/primitives/**/{*,.*}',
])
reviewers:
users:
- csmick # Cameron Smick
- lannka # Hongfei Ding
- eduhmc # Eduardo Huerta-Mercado
- ehlemur # Edward Lesmes
- ellenyuan # Ellen Yuan
- jatraman # Jatin Ramanathan
- jesse-good # Jesse Costello-Good
- mturco # Matt Turco
- neonstalwart # Ben Hockey
- nicholasyu-google # Nicholas Yu
- emspishak # Eric Spishak-Thomas
- iteriani # Thomas Nguyen
- tbondwilkinson # Tom Wilkinson
- rahatarmanahmed # Rahat Ahmed
reviews:
required: 1
reviewed_for: required
labels:
pending: 'requires: TGP'
approved: 'requires: TGP'
rejected: 'requires: TGP'
####################################################################################
# Override managed result groups
#
-5
View File
@@ -5,12 +5,7 @@
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"BazelBuild.vscode-bazel",
"gkalpak.aio-docs-utils",
"ms-vscode.vscode-typescript-tslint-plugin",
"xaver.clang-format",
// The following extensions are useful when working on angular.io (i.e. inside the `aio/` directory).
//"angular.ng-template",
//"dbaeumer.vscode-eslint",
//"errata-ai.vale-server",
],
}
+46 -89
View File
@@ -1,108 +1,25 @@
<a name="17.3.4"></a>
# 17.3.4 (2024-04-10)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [53427d875d](https://github.com/angular/angular/commit/53427d875d33de16b1dbb04e76a9fe32a2b8a445) | fix | invalid ImageKit quality parameter ([#55193](https://github.com/angular/angular/pull/55193)) |
| [766548c3ec](https://github.com/angular/angular/commit/766548c3ecc06ffd8f06ed9a7d0cbb3db6f1b0f6) | fix | skip transfer cache on client ([#55012](https://github.com/angular/angular/pull/55012)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.3.3"></a>
# 17.3.3 (2024-04-03)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [158ceaf062](https://github.com/angular/angular/commit/158ceaf062036bde2a828ee769580f99433a31a8) | fix | handle `ChainedInjector`s in injector debug utils ([#55144](https://github.com/angular/angular/pull/55144)) |
| [4d043992e5](https://github.com/angular/angular/commit/4d043992e5635a2f841b633fb38d0009a39f05d0) | fix | test cleanup should not throw if Zone is not present ([#55096](https://github.com/angular/angular/pull/55096)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [949dec26b8](https://github.com/angular/angular/commit/949dec26b860a500e90874ad778da74549d21d7e) | fix | avoid conflicts with some greek letters in control flow migration ([#55113](https://github.com/angular/angular/pull/55113)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.3.2"></a>
# 17.3.2 (2024-03-28)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [2b7bad5151](https://github.com/angular/angular/commit/2b7bad515100cbfa40b3e8d844bae13d43fd5602) | fix | invoke method-based tracking function with context ([#54960](https://github.com/angular/angular/pull/54960)) |
<a name="17.3.0-rc.0"></a>
# 17.3.0-rc.0 (2024-03-06)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [b478dfbfda](https://github.com/angular/angular/commit/b478dfbfda3f32fbe723a1e8725e86490422520d) | fix | report errors when initializer APIs are used on private fields ([#55070](https://github.com/angular/angular/pull/55070)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [708ba8115f](https://github.com/angular/angular/commit/708ba8115f7ad05201db5c529aefe4dd48fc64c5) | fix | establish proper injector resolution order for `@defer` blocks ([#55079](https://github.com/angular/angular/pull/55079)) |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [cb433af0e1](https://github.com/angular/angular/commit/cb433af0e1ba61073eb4a02ccd75cf360e9fd409) | fix | include transferCache when cloning HttpRequest ([#54939](https://github.com/angular/angular/pull/54939)) |
| [64f202cab9](https://github.com/angular/angular/commit/64f202cab9e7a5c873b17bbddd02368006426152) | fix | manage different body types for caching POST requests ([#54980](https://github.com/angular/angular/pull/54980)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [2f9d94bc4a](https://github.com/angular/angular/commit/2f9d94bc4ab5a94e620a13404aba4e094f8b2344) | fix | account for variables in imports initializer ([#55081](https://github.com/angular/angular/pull/55081)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [365fd50407](https://github.com/angular/angular/commit/365fd504077d0e7509efc3077ea4ae8bbafb01f7) | fix | RouterLinkActive will always remove active classes when links are not active ([#54982](https://github.com/angular/angular/pull/54982)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.3.1"></a>
# 17.3.1 (2024-03-20)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [c0788200e2](https://github.com/angular/angular/commit/c0788200e26406bacb90ee0079ea9753eebd1b32) | fix | capture data bindings for content projection purposes in blocks ([#54876](https://github.com/angular/angular/pull/54876)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [99e9474aa2](https://github.com/angular/angular/commit/99e9474aa2ccccdf4385fbac6b62b1a4585ed4b4) | fix | symbol feature detection for the compiler ([#54711](https://github.com/angular/angular/pull/54711)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.3.0"></a>
# 17.3.0 (2024-03-13)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [1a6beae8a2](https://github.com/angular/angular/commit/1a6beae8a2bdcff27d4c1e402f98246a52247906) | feat | Enable template pipeline by default. ([#54571](https://github.com/angular/angular/pull/54571)) |
| [f386a04c9d](https://github.com/angular/angular/commit/f386a04c9ddc2951c8105d5cafcce7b4bedea569) | fix | handle two-way bindings to signal-based template variables in instruction generation ([#54714](https://github.com/angular/angular/pull/54714)) |
| [1f129f114e](https://github.com/angular/angular/commit/1f129f114edb21137d74471dd6b652848d2ffb68) | fix | not catching for loop empty tracking expressions ([#54772](https://github.com/angular/angular/pull/54772)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [12dc4d074e](https://github.com/angular/angular/commit/12dc4d074e63edaff626003ad6136a8d122b2ba6) | fix | account for as expression in docs extraction ([#54414](https://github.com/angular/angular/pull/54414)) |
| [da7fbb40f0](https://github.com/angular/angular/commit/da7fbb40f06e6e37504f69e7b335f8219f424de2) | fix | detect when the linker is working in unpublished angular and widen supported versions ([#54439](https://github.com/angular/angular/pull/54439)) |
| [492e03f699](https://github.com/angular/angular/commit/492e03f699ca95df4cf854885253f63a5a1fb5c1) | fix | flag two-way bindings to non-signal values in templates ([#54714](https://github.com/angular/angular/pull/54714)) |
| [5afa4f0ec1](https://github.com/angular/angular/commit/5afa4f0ec1b64b88ef875d48bd143e0f36e0a955) | fix | support `ModuleWithProviders` literal detection with `typeof` ([#54650](https://github.com/angular/angular/pull/54650)) |
| [ae7dbe42de](https://github.com/angular/angular/commit/ae7dbe42de5779e578fb636938699b8ba85b33f6) | fix | unwrap expressions with type parameters in query read property ([#54647](https://github.com/angular/angular/pull/54647)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [331b16efd2](https://github.com/angular/angular/commit/331b16efd2f5af876e6dc0ad2474ee7a87b00de5) | feat | add API to inject attributes on the host node ([#54604](https://github.com/angular/angular/pull/54604)) |
| [fb540e169a](https://github.com/angular/angular/commit/fb540e169a78a61f38d611f538eea8fdb0971f1d) | feat | add migration for invalid two-way bindings ([#54630](https://github.com/angular/angular/pull/54630)) |
| [c687b8f453](https://github.com/angular/angular/commit/c687b8f4531252cd1c3dfbb9a7bd42bdbe666a36) | feat | expose new `output()` API ([#54650](https://github.com/angular/angular/pull/54650)) |
| [c809069f21](https://github.com/angular/angular/commit/c809069f213244afd0e2d803a6a43510b218e6f5) | feat | introduce `outputFromObservable()` interop function ([#54650](https://github.com/angular/angular/pull/54650)) |
| [aff65fd1f4](https://github.com/angular/angular/commit/aff65fd1f4a61ed76a6f9b623852f197eb3500e4) | feat | introduce `outputToObservable` interop helper ([#54650](https://github.com/angular/angular/pull/54650)) |
| [974958913c](https://github.com/angular/angular/commit/974958913ca632971f878a045537472f2c99c665) | feat | support TypeScript 5.4 ([#54414](https://github.com/angular/angular/pull/54414)) |
| [39a50f9a8d](https://github.com/angular/angular/commit/39a50f9a8df5afc4968d18924f3d9d7d6b649d3a) | fix | ensure all initializer functions run in an injection context ([#54761](https://github.com/angular/angular/pull/54761)) |
| [243ccce624](https://github.com/angular/angular/commit/243ccce62475ae03a2e727d2b3cb2d51a595c4a7) | fix | exclude class attribute intended for projection matching from directive matching ([#54800](https://github.com/angular/angular/pull/54800)) |
| [2909e9817d](https://github.com/angular/angular/commit/2909e9817daf69b6478d7d09229491a9a48cff16) | fix | prevent infinite loops in clobbered elements check ([#54425](https://github.com/angular/angular/pull/54425)) |
| [33a6fab094](https://github.com/angular/angular/commit/33a6fab094205cc74aceb916e45c8afa22293cf4) | fix | apply TestBed provider overrides to `@defer` dependencies ([#54667](https://github.com/angular/angular/pull/54667)) |
| [78e69117f0](https://github.com/angular/angular/commit/78e69117f07e8ae11aab3abe0343df815b3649be) | fix | generic inference for signal inputs may break with `--strictFunctionTypes` ([#54652](https://github.com/angular/angular/pull/54652)) |
| [7243c704cf](https://github.com/angular/angular/commit/7243c704cf8a4986fae419793027458e142658f0) | fix | return a readonly signal on `asReadonly`. ([#54706](https://github.com/angular/angular/pull/54706)) |
| [bb35414a38](https://github.com/angular/angular/commit/bb35414a38f8a8447d03c66dc5bf60b84409f9e3) | perf | speed up retrieval of `DestroyRef` in `EventEmitter` ([#54748](https://github.com/angular/angular/pull/54748)) |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [8d37ed035c](https://github.com/angular/angular/commit/8d37ed035c9e9796ba9a7b1f055404ea220dbb3b) | fix | exclude caching for authenticated HTTP requests ([#54746](https://github.com/angular/angular/pull/54746)) |
| [ffad7b8ea9](https://github.com/angular/angular/commit/ffad7b8ea9d1286ddb3ee7a2dac7dd33fd76b3aa) | fix | untrack various core operations ([#54614](https://github.com/angular/angular/pull/54614)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [c1c7384e02](https://github.com/angular/angular/commit/c1c7384e02becc623c6a42985f7178ca98137264) | feat | Add reusable types for router guards ([#54580](https://github.com/angular/angular/pull/54580)) |
| [7225485311](https://github.com/angular/angular/commit/722548531108e247660ebe6966b99b57d510b615) | fix | Navigations triggered by cancellation events should cancel previous navigation ([#54710](https://github.com/angular/angular/pull/54710)) |
<!-- CHANGELOG SPLIT MARKER -->
@@ -146,6 +63,46 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.3.0-next.1"></a>
# 17.3.0-next.1 (2024-02-27)
### common
| Commit | Type | Description |
| -- | -- | -- |
| [707bfc9b32](https://github.com/angular/angular/commit/707bfc9b326f321f302dccfdfebef7380c914eb5) | perf | `AsyncPipe` should not call `markForCheck` on subscription ([#54554](https://github.com/angular/angular/pull/54554)) |
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [1a6beae8a2](https://github.com/angular/angular/commit/1a6beae8a2bdcff27d4c1e402f98246a52247906) | feat | Enable template pipeline by default. ([#54571](https://github.com/angular/angular/pull/54571)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [12dc4d074e](https://github.com/angular/angular/commit/12dc4d074e63edaff626003ad6136a8d122b2ba6) | fix | account for as expression in docs extraction ([#54414](https://github.com/angular/angular/pull/54414)) |
| [f578889ca2](https://github.com/angular/angular/commit/f578889ca2dbb22f2e050d4c4af6aa2d597bf70d) | fix | catch function instance properties in interpolated signal diagnostic ([#54325](https://github.com/angular/angular/pull/54325)) |
| [f04ecc0cda](https://github.com/angular/angular/commit/f04ecc0cdaeb3a292a748b1ccc94ce70a573fc79) | fix | identify aliased initializer functions ([#54480](https://github.com/angular/angular/pull/54480)) |
| [f5c566c079](https://github.com/angular/angular/commit/f5c566c0793eacf9ca146c8a6b8da15b0e8f4c4d) | fix | identify aliased initializer functions ([#54609](https://github.com/angular/angular/pull/54609)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [331b16efd2](https://github.com/angular/angular/commit/331b16efd2f5af876e6dc0ad2474ee7a87b00de5) | feat | add API to inject attributes on the host node ([#54604](https://github.com/angular/angular/pull/54604)) |
| [974958913c](https://github.com/angular/angular/commit/974958913ca632971f878a045537472f2c99c665) | feat | support TypeScript 5.4 ([#54414](https://github.com/angular/angular/pull/54414)) |
| [dcb9deb363](https://github.com/angular/angular/commit/dcb9deb3631ff4e839f738f2fb97ca6b894256ef) | fix | collect providers from NgModules while rendering `@defer` block ([#52881](https://github.com/angular/angular/pull/52881)) |
| [ff40c9f762](https://github.com/angular/angular/commit/ff40c9f762b2c14870ac2859201ece9660087e79) | fix | fix typo in injectors.svg file ([#54596](https://github.com/angular/angular/pull/54596)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [71e0c7df69](https://github.com/angular/angular/commit/71e0c7df695f274a54d5ffb9dfc0587d06d3a953) | fix | resolve infinite loop for a single line element with a long tag name and angle bracket on a new line ([#54588](https://github.com/angular/angular/pull/54588)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.3.0-next.0"></a>
# 17.3.0-next.0 (2024-02-21)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [da7fbb40f0](https://github.com/angular/angular/commit/da7fbb40f06e6e37504f69e7b335f8219f424de2) | fix | detect when the linker is working in unpublished angular and widen supported versions ([#54439](https://github.com/angular/angular/pull/54439)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="17.2.2"></a>
# 17.2.2 (2024-02-21)
### common
+5
View File
@@ -389,6 +389,7 @@ The following documents can help you sort out issues with GitHub accounts and mu
[angular-group]: https://groups.google.com/forum/#!forum/angular
[coc]: https://github.com/angular/code-of-conduct/blob/main/CODE_OF_CONDUCT.md
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[corporate-cla]: https://cla.developers.google.com/about/google-corporate
@@ -397,3 +398,7 @@ The following documents can help you sort out issues with GitHub accounts and mu
[discord]: https://discord.gg/angular
[individual-cla]: https://cla.developers.google.com/about/google-individual
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
[jsfiddle]: https://jsfiddle.net/
[plunker]: https://plnkr.co/edit
[runnable]: https://runnable.com/
[stackoverflow]: https://stackoverflow.com/questions/tagged/angular
+3 -3
View File
@@ -1,7 +1,7 @@
<h1 align="center">Angular - The modern web developer's platform</h1>
<p align="center">
<img src="adev/src/assets/images/press-kit/angular_icon_gradient.gif" alt="angular-logo" width="120px" height="120px"/>
<img src="aio/src/assets/images/logos/angular/angular_renaissance.png" alt="angular-logo" width="120px" height="120px"/>
<br>
<em>Angular is a development platform for building mobile and desktop web applications
<br> using TypeScript/JavaScript and other languages.</em>
@@ -167,8 +167,8 @@ Join the conversation and help the community.
[angularmaterial]: https://material.angular.io/
[cli]: https://angular.dev/tools/cli
[architecture]: https://angular.dev/essentials
[componentstemplates]: https://angular.dev/tutorials/learn-angular/1-components-in-angular
[forms]: https://angular.dev/tutorials/learn-angular/15-forms
[componentstemplates]: https://angular.dev/tutorials/learn-angular/components-in-angular
[forms]: https://angular.dev/tutorials/learn-angular/forms
[api]: https://angular.dev/api
[angularelements]: https://angular.dev/guide/elements
[ssr]: https://angular.dev/guide/ssr
+3 -3
View File
@@ -206,10 +206,10 @@ cldr_xml_data_repository(
# sass rules
http_archive(
name = "io_bazel_rules_sass",
sha256 = "f6d8058da1bad8d4eac74f3220f74212ad06b355eea6387251f161e0a17e29e3",
strip_prefix = "rules_sass-147f7fa5210c89e58c1a4b99245c7daf64c4c8ad",
sha256 = "974f5e14c54d300ca63a67910ebfcea6d838d8811fefc61184f2872107152705",
strip_prefix = "rules_sass-888dd9c02709e228b60a8b3e51e2b16b2ca5279d",
urls = [
"https://github.com/bazelbuild/rules_sass/archive/147f7fa5210c89e58c1a4b99245c7daf64c4c8ad.zip",
"https://github.com/bazelbuild/rules_sass/archive/888dd9c02709e228b60a8b3e51e2b16b2ca5279d.zip",
],
)
+18 -29
View File
@@ -1,9 +1,8 @@
load("@npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
load("//:packages.bzl", "link_packages")
load("@aio_npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
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")
load("@bazel_skylib//lib:collections.bzl", "collections")
load("//adev/tools/local_deps:index.bzl", "ensure_local_package_deps", "link_local_packages")
package(default_visibility = ["//visibility:public"])
@@ -31,17 +30,7 @@ TEST_FILES = APPLICATION_FILES + [
["**/*.spec.ts"],
)
APPLICATION_ASSETS = [
"//adev/src/assets/images",
"//adev/src/assets/textures",
"//adev/src/assets/previews",
"//adev/src/assets:tutorials",
"//adev/src/assets/icons",
"//adev/src/assets:api",
"//adev/src/assets:content",
]
APPLICATION_DEPS = [
APPLICATION_DEPS = link_packages([
"@npm//@angular-devkit/build-angular",
"@npm//@angular/animations",
"@npm//@angular/cdk",
@@ -61,6 +50,13 @@ APPLICATION_DEPS = [
"@npm//ogl",
"@npm//rxjs",
"@npm//typescript",
"//adev/src/assets/images",
"//adev/src/assets/textures",
"//adev/src/assets/previews",
"//adev/src/assets:tutorials",
"//adev/src/assets/icons",
"//adev/src/assets:api",
"//adev/src/assets:content",
"@npm//@typescript/vfs",
"@npm//@codemirror/state",
"@npm//@codemirror/view",
@@ -80,10 +76,11 @@ APPLICATION_DEPS = [
"@npm//xterm",
"@npm//xterm-addon-fit",
"@npm//angular-split",
]
])
TEST_DEPS = APPLICATION_DEPS + [
TEST_DEPS = APPLICATION_DEPS + link_packages([
"@npm//@angular/platform-browser-dynamic",
"@npm//@angular/build-tooling/bazel/browsers/chromium",
"@npm//@types/jasmine",
"@npm//@types/node",
"@npm//assert",
@@ -93,13 +90,8 @@ TEST_DEPS = APPLICATION_DEPS + [
"@npm//karma-coverage",
"@npm//karma-jasmine",
"@npm//karma-jasmine-html-reporter",
]
# Create `npm_link` targets for all dependencies that correspond to a
# first-party Angular package that can be built from `HEAD`.
link_local_packages(
all_deps = collections.uniq(APPLICATION_DEPS + TEST_DEPS),
)
"//aio/tools:windows-chromium-path",
])
copy_to_bin(
name = "application_files_bin",
@@ -137,7 +129,7 @@ architect(
"--output-path=build",
] + config_based_architect_flags,
chdir = "$(RULEDIR)",
data = ensure_local_package_deps(APPLICATION_DEPS) + APPLICATION_ASSETS + [
data = APPLICATION_DEPS + [
":application_files_bin",
],
# Network is required to inline fonts.
@@ -157,7 +149,7 @@ architect(
"--watch",
],
chdir = package_name(),
data = ensure_local_package_deps(APPLICATION_DEPS) + APPLICATION_ASSETS + [
data = APPLICATION_DEPS + [
":application_files_bin",
],
tags = [
@@ -172,10 +164,7 @@ architect_test(
"--no-watch",
],
chdir = package_name(),
data = ensure_local_package_deps(TEST_DEPS) + TEST_FILES + APPLICATION_ASSETS + [
"//aio/tools:windows-chromium-path",
"@npm//@angular/build-tooling/bazel/browsers/chromium",
],
data = TEST_DEPS + TEST_FILES,
env = {
"CHROME_BIN": "../$(CHROMIUM)",
},
+9 -8
View File
@@ -19,7 +19,9 @@
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"externalDependencies": ["path"],
"externalDependencies": [
"path"
],
"outputPath": "dist/angular-dev",
"index": "src/index.html",
"browser": "src/main.ts",
@@ -27,7 +29,11 @@
"polyfills": ["src/polyfills.ts", "zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/robots.txt", "src/assets"],
"assets": [
"src/favicon.ico",
"src/robots.txt",
"src/assets"
],
"styles": ["@angular/docs/styles/global-styles.scss"],
"scripts": [],
"webWorkerTsConfig": "tsconfig.worker.json",
@@ -93,11 +99,6 @@
}
},
"cli": {
"analytics": "dca119a9-da31-47f7-a6cb-b60541037021",
"cache": {
// Disable build caching as the cache folder will just be dropped
// when run under Bazel sandboxed execution.
"enabled": false
}
"analytics": "dca119a9-da31-47f7-a6cb-b60541037021"
}
}
-74
View File
@@ -1,74 +0,0 @@
/*!
* @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
*/
export const VERSIONS_CONFIG = {
currentVersion: 'stable',
historicalVersionsLinkPattern: 'https://v{{version}}.angular.dev',
mainVersions: [
{
version: 'stable',
url: 'https://angular.dev',
},
{
version: 'v16',
url: 'https://v16.angular.io/docs',
},
{
version: 'v15',
url: 'https://v15.angular.io/docs',
},
{
version: 'v14',
url: 'https://v14.angular.io/docs',
},
{
version: 'v13',
url: 'https://v13.angular.io/docs',
},
{
version: 'v12',
url: 'https://v12.angular.io/docs',
},
{
version: 'v11',
url: 'https://v11.angular.io/docs',
},
{
version: 'v10',
url: 'https://v10.angular.io/docs',
},
{
version: 'v9',
url: 'https://v9.angular.io/docs',
},
{
version: 'v8',
url: 'https://v8.angular.io/docs',
},
{
version: 'v7',
url: 'https://v7.angular.io/docs',
},
{
version: 'v6',
url: 'https://v6.angular.io/docs',
},
{
version: 'v5',
url: 'https://v5.angular.io/docs',
},
{
version: 'v4',
url: 'https://v4.angular.io/docs',
},
{
version: 'v2',
url: 'https://v2.angular.io/docs',
},
],
};
@@ -10,7 +10,6 @@ import {HttpClient} from '@angular/common/http';
import {Injectable, inject} from '@angular/core';
import {DocContent, DocsContentLoader} from '@angular/docs';
import {Router} from '@angular/router';
import {firstValueFrom} from 'rxjs';
import {map} from 'rxjs/operators';
@Injectable()
@@ -28,13 +27,12 @@ export class ContentLoader implements DocsContentLoader {
try {
this.cache.set(
path,
firstValueFrom(
this.httpClient
.get(`assets/content/${path}`, {
responseType: 'text',
})
.pipe(map((contents) => ({contents, id: path}))),
),
this.httpClient
.get(`assets/content/${path}`, {
responseType: 'text',
})
.pipe(map((contents) => ({contents, id: path})))
.toPromise(),
);
} catch {
this.router.navigateByUrl('/404');
@@ -82,7 +82,7 @@ export class ThemeManager {
}
private watchPreferredColorScheme() {
window.matchMedia(PREFERS_COLOR_SCHEME_DARK).addEventListener('change', (event) => {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {
const preferredScheme = event.matches ? 'dark' : 'light';
this.setThemeBodyClasses(preferredScheme);
});
@@ -90,5 +90,5 @@ export class ThemeManager {
}
function preferredScheme(): 'dark' | 'light' {
return window.matchMedia(PREFERS_COLOR_SCHEME_DARK).matches ? 'dark' : 'light';
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
@@ -7,7 +7,74 @@
*/
import {Injectable, VERSION, computed, signal} from '@angular/core';
import {VERSIONS_CONFIG} from '../constants/versions';
// TODO(josephperrott): extract this out of the file into a managed location.
const VERSIONS_CONFIG = {
currentVersion: 'stable',
historicalVersionsLinkPattern: 'https://v{{version}}.angular.dev',
mainVersions: [
{
version: 'stable',
url: 'https://angular.dev',
},
{
version: 'v16',
url: 'https://v16.angular.io/docs',
},
{
version: 'v15',
url: 'https://v15.angular.io/docs',
},
{
version: 'v14',
url: 'https://v14.angular.io/docs',
},
{
version: 'v13',
url: 'https://v13.angular.io/docs',
},
{
version: 'v12',
url: 'https://v12.angular.io/docs',
},
{
version: 'v11',
url: 'https://v11.angular.io/docs',
},
{
version: 'v10',
url: 'https://v10.angular.io/docs',
},
{
version: 'v9',
url: 'https://v9.angular.io/docs',
},
{
version: 'v8',
url: 'https://v8.angular.io/docs',
},
{
version: 'v7',
url: 'https://v7.angular.io/docs',
},
{
version: 'v6',
url: 'https://v6.angular.io/docs',
},
{
version: 'v5',
url: 'https://v5.angular.io/docs',
},
{
version: 'v4',
url: 'https://v4.angular.io/docs',
},
{
version: 'v2',
url: 'https://v2.angular.io/docs',
},
],
};
export interface Version {
displayName: string;
File diff suppressed because one or more lines are too long
@@ -36,7 +36,6 @@
box-sizing: border-box;
transition: background 0.3s ease, border 0.3s ease;
h1,
p {
display: inline;
font-size: 0.875rem;
@@ -108,6 +108,7 @@
& > .docs-code {
box-sizing: border-box;
width: 100%;
max-height: 93vh;
overflow: hidden;
padding: 0;
@@ -118,7 +119,6 @@
button {
transition: background-color 0.3s ease;
font-family: monospace;
&.hljs-ln-line {
font-weight: 400;
@@ -154,10 +154,6 @@
}
}
.docs-reference-cli-toc {
margin-bottom: 1rem;
}
.adev-reference-tab {
min-width: 50ch;
margin-block-start: 2.5rem;
@@ -271,9 +267,7 @@
background-color: var(--octonary-contrast);
position: relative;
z-index: 10;
transition:
background-color 0.3s ease,
border 0.3s ease;
transition: background-color 0.3s ease, border 0.3s ease;
// h3 + code || # of overloads
.docs-reference-header {
@@ -410,17 +404,4 @@
margin-block-start: 0;
}
}
.docs-ref-content {
padding: 1rem 0;
&:not(:first-child) {
border-block-start: 1px solid var(--senary-contrast);
}
.docs-param-keyword {
display: block;
margin: 0 0 0.5rem 0;
}
}
}
@@ -3,11 +3,6 @@
<h6>Getting Started</h6>
<h1 tabindex="-1">API Reference</h1>
</header>
@if (featuredGroup().items.length) {
<adev-api-items-section [group]="featuredGroup()" class="adev-featured-list" />
}
<form class="adev-reference-list-form">
<docs-text-field
name="query"
@@ -27,20 +22,23 @@
</div>
<ul class="adev-reference-list-legend">
@for (itemType of itemTypes; track itemType) {
<li
class="adev-reference-list-legend-item"
[class.adev-reference-list-legend-item-active]="type() === itemType"
(click)="filterByItemType(itemType)"
>
<docs-api-item-label [type]="itemType" mode="short" class="docs-api-item-label" />
<span class="docs-api-item-label-full">{{ itemType | adevApiLabel : 'full' }}</span>
</li>
<li
class="adev-reference-list-legend-item"
[class.adev-reference-list-legend-item-active]="type() === itemType"
(click)="filterByItemType(itemType)"
>
<docs-api-item-label [type]="itemType" mode="short" class="docs-api-item-label" />
<span>{{ itemType | adevApiLabel : 'full' }}</span>
</li>
}
</ul>
</form>
@for (group of filteredGroups(); track group) {
<adev-api-items-section [group]="group" />
@if (featuredGroup().items.length) {
<adev-api-items-section [group]="featuredGroup()" class="adev-featured-list" />
} @for (group of filteredGroups(); track group) {
<adev-api-items-section [group]="group" />
}
<span id="deprecated-description" class="docs-deprecated-description">Deprecated</span>
</div>
@@ -77,9 +77,8 @@
justify-content: flex-start;
flex-wrap: wrap-reverse;
gap: 1.5rem;
padding-block-start: 1.5rem;
padding-block-start: 0;
padding-block-end: 2rem;
border-block: 1px solid var(--senary-contrast);
}
.adev-reference-list-form-part-two {
@@ -90,12 +89,13 @@
.adev-featured-list {
display: block;
padding-block-end: 1rem;
padding-block-end: 2rem;
padding-block-start: 1.5rem;
margin-block: 1rem;
border-top: 0;
margin-top: -1.5rem;
border-block: 1px solid var(--senary-contrast);
}
.docs-api-item-label-full {
white-space: nowrap;
.docs-deprecated-description {
display: none;
}
@@ -14,7 +14,7 @@ import {ApiItemsGroup} from '../interfaces/api-items-group';
import {ApiManifest} from '../interfaces/api-manifest';
export const FEATURED_API_ITEMS_KEY = 'apiFeaturedItems';
export const FEATURED_GROUP_TITLE = 'Most Common';
export const FEATURED_GROUP_TITLE = 'Featured';
export type FeaturedItemsByGroup = Record<string, ApiItem[]>;
@@ -19,5 +19,4 @@ export enum ApiItemType {
PIPE = 'pipe',
NG_MODULE = 'ng_module',
TYPE_ALIAS = 'type_alias',
INITIALIZER_API_FUNCTION = 'initializer_api_function',
}
@@ -7,7 +7,6 @@
*/
import {Pipe, PipeTransform} from '@angular/core';
import {ApiItemType} from '../interfaces/api-item-type';
@Pipe({
@@ -28,7 +27,6 @@ export class ApiLabel implements PipeTransform {
[ApiItemType.PIPE]: 'P',
[ApiItemType.NG_MODULE]: 'M',
[ApiItemType.TYPE_ALIAS]: 'T',
[ApiItemType.INITIALIZER_API_FUNCTION]: 'IA',
};
private readonly fullLabelsMap: Record<ApiItemType, string> = {
@@ -44,7 +42,6 @@ export class ApiLabel implements PipeTransform {
[ApiItemType.PIPE]: 'Pipe',
[ApiItemType.NG_MODULE]: 'Module',
[ApiItemType.TYPE_ALIAS]: 'Type Alias',
[ApiItemType.INITIALIZER_API_FUNCTION]: 'Initializer API',
};
transform(value: ApiItemType, labelType: 'short' | 'full'): string {
@@ -160,6 +160,9 @@ export class ReferenceScrollHandler implements OnDestroy, ReferenceScrollHandler
for (const line of Array.from(activeLines)) {
line.classList.remove(API_TAB_ACTIVE_CODE_LINE);
}
this.getAllMemberCards().forEach((card) => {
card.blur();
});
} else {
const lines = this.document.querySelectorAll<HTMLButtonElement>(
`button[${MEMBER_ID_ATTRIBUTE}="${currentActiveMemberId}"]`,
@@ -167,7 +170,7 @@ export class ReferenceScrollHandler implements OnDestroy, ReferenceScrollHandler
for (const line of Array.from(lines)) {
line.classList.add(API_TAB_ACTIVE_CODE_LINE);
}
this.document.getElementById(`${currentActiveMemberId}`)?.focus({preventScroll: true});
this.document.getElementById(`${currentActiveMemberId}`)?.focus();
}
}
@@ -176,9 +179,7 @@ export class ReferenceScrollHandler implements OnDestroy, ReferenceScrollHandler
return;
}
if (card !== <HTMLElement>document.activeElement) {
(<HTMLElement>document.activeElement).blur();
}
card.focus();
this.window.scrollTo({
top: card!.offsetTop - this.membersMarginTopInPx(),
@@ -210,9 +211,8 @@ export class ReferenceScrollHandler implements OnDestroy, ReferenceScrollHandler
this.resizeObserver = new ResizeObserver((_) => {
this.ngZone.run(() => {
const offsetTop = tabBody.getBoundingClientRect().top;
if (offsetTop) {
this.membersMarginTopInPx.set(offsetTop);
if (tabBody.offsetTop) {
this.membersMarginTopInPx.set(tabBody.offsetTop);
}
});
});
-5
View File
@@ -117,11 +117,6 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
path: 'guide/components/outputs',
contentPath: 'guide/components/outputs',
},
{
label: 'output() function',
path: 'guide/components/output-fn',
contentPath: 'guide/components/output-function',
},
{
label: 'Content projection with ng-content',
path: 'guide/components/content-projection',
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Before

Width:  |  Height:  |  Size: 3.4 MiB

After

Width:  |  Height:  |  Size: 3.4 MiB

+6 -6
View File
@@ -17,16 +17,16 @@ Use attribute binding template syntax to control the values of accessibility-rel
When binding to ARIA attributes in Angular, you must use the `attr.` prefix. The ARIA specification depends specifically on HTML attributes rather than properties of DOM elements.
<docs-code language="html">
<!-- Use attr. when binding to an ARIA attribute -->
<button [attr.aria-label]="myActionLabel">…</button>
&lt;!-- Use attr. when binding to an ARIA attribute --&gt;
&lt;button [attr.aria-label]="myActionLabel"&gt;&hellip;&lt;/button&gt;
</docs-code>
Note: This syntax is only necessary for attribute *bindings*.
Static ARIA attributes require no extra syntax.
<docs-code language="html">
<!-- Static ARIA attributes require no extra syntax -->
<button aria-label="Save document">…</button>
&lt;!-- Static ARIA attributes require no extra syntax --&gt;
&lt;button aria-label="Save document"&gt;&hellip;&lt;/button&gt;
</docs-code>
HELPFUL: By convention, HTML attributes use lowercase names \(`tabindex`\), while properties use camelCase names \(`tabIndex`\).
@@ -103,8 +103,8 @@ The following example shows how to find and focus the main content header in the
<docs-code language="typescript">
router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe(() => {
const mainHeader = document.querySelector('#main-content-header')
router.events.pipe(filter(e =&gt; e instanceof NavigationEnd)).subscribe(() =&gt; {
const mainHeader = document.querySelector('&num;main-content-header')
if (mainHeader) {
mainHeader.focus();
}
+38 -38
View File
@@ -151,14 +151,14 @@ Consistent conventions make it easy to quickly identify and reference assets of
| Symbol name | File name |
|:--- |:--- |
| <docs-code hideCopy language="typescript"> @Component({ … }) <br>export class AppComponent { } </docs-code> | app.component.ts |
| <docs-code hideCopy language="typescript"> @Component({ … }) <br>export class HeroesComponent { } </docs-code> | heroes.component.ts |
| <docs-code hideCopy language="typescript"> @Component({ … }) <br>export class HeroListComponent { } </docs-code> | hero-list.component.ts |
| <docs-code hideCopy language="typescript"> @Component({ … }) <br>export class HeroDetailComponent { } </docs-code> | hero-detail.component.ts |
| <docs-code hideCopy language="typescript"> @Directive({ … }) <br>export class ValidationDirective { } </docs-code> | validation.directive.ts |
| <docs-code hideCopy language="typescript"> @NgModule({ … }) <br>export class AppModule </docs-code> | app.module.ts |
| <docs-code hideCopy language="typescript"> @Pipe({ name: 'initCaps' }) <br>export class InitCapsPipe implements PipeTransform { } </docs-code> | init-caps.pipe.ts |
| <docs-code hideCopy language="typescript"> @Injectable() <br>export class UserProfileService { } </docs-code> | user-profile.service.ts |
| <docs-code hideCopy language="typescript"> &commat;Component({ &hellip; }) &NewLine;export class AppComponent { } </docs-code> | app.component.ts |
| <docs-code hideCopy language="typescript"> &commat;Component({ &hellip; }) &NewLine;export class HeroesComponent { } </docs-code> | heroes.component.ts |
| <docs-code hideCopy language="typescript"> &commat;Component({ &hellip; }) &NewLine;export class HeroListComponent { } </docs-code> | hero-list.component.ts |
| <docs-code hideCopy language="typescript"> &commat;Component({ &hellip; }) &NewLine;export class HeroDetailComponent { } </docs-code> | hero-detail.component.ts |
| <docs-code hideCopy language="typescript"> &commat;Directive({ &hellip; }) &NewLine;export class ValidationDirective { } </docs-code> | validation.directive.ts |
| <docs-code hideCopy language="typescript"> &commat;NgModule({ &hellip; }) &NewLine;export class AppModule </docs-code> | app.module.ts |
| <docs-code hideCopy language="typescript"> &commat;Pipe({ name: 'initCaps' }) &NewLine;export class InitCapsPipe implements PipeTransform { } </docs-code> | init-caps.pipe.ts |
| <docs-code hideCopy language="typescript"> &commat;Injectable() &NewLine;export class UserProfileService { } </docs-code> | user-profile.service.ts |
### Service names
@@ -186,9 +186,9 @@ Service names such as `Credit` are nouns and require a suffix and should be name
| Symbol name | File name |
|:--- |:--- |
| <docs-code hideCopy language="typescript"> @Injectable() <br>export class HeroDataService { } </docs-code> | hero-data.service.ts |
| <docs-code hideCopy language="typescript"> @Injectable() <br>export class CreditService { } </docs-code> | credit.service.ts |
| <docs-code hideCopy language="typescript"> @Injectable() <br>export class Logger { } </docs-code> | logger.service.ts |
| <docs-code hideCopy language="typescript"> &commat;Injectable() &NewLine;export class HeroDataService { } </docs-code> | hero-data.service.ts |
| <docs-code hideCopy language="typescript"> &commat;Injectable() &NewLine;export class CreditService { } </docs-code> | credit.service.ts |
| <docs-code hideCopy language="typescript"> &commat;Injectable() &NewLine;export class Logger { } </docs-code> | logger.service.ts |
### Bootstrapping
@@ -293,8 +293,8 @@ Provides a consistent way to quickly identify and reference pipes.
| Symbol name | File name |
|:--- |:--- |
| <docs-code hideCopy language="typescript"> @Pipe({ standalone: true, name: 'ellipsis' }) <br>export class EllipsisPipe implements PipeTransform { } </docs-code> | ellipsis.pipe.ts |
| <docs-code hideCopy language="typescript"> @Pipe({ standalone: true, name: 'initCaps' }) <br>export class InitCapsPipe implements PipeTransform { } </docs-code> | init-caps.pipe.ts |
| <docs-code hideCopy language="typescript"> &commat;Pipe({ standalone: true, name: 'ellipsis' }) &NewLine;export class EllipsisPipe implements PipeTransform { } </docs-code> | ellipsis.pipe.ts |
| <docs-code hideCopy language="typescript"> &commat;Pipe({ standalone: true, name: 'initCaps' }) &NewLine;export class InitCapsPipe implements PipeTransform { } </docs-code> | init-caps.pipe.ts |
### Unit test file names
@@ -354,40 +354,40 @@ project root
├── src
│ ├── app
│ │ ├── core
│ │ │ └── exception.service.ts|spec.ts
│ │ │ └── user-profile.service.ts|spec.ts
│ │ │ └── exception.service.ts&verbar;spec.ts
│ │ │ └── user-profile.service.ts&verbar;spec.ts
│ │ ├── heroes
│ │ │ ├── hero
│ │ │ │ └── hero.component.ts|html|css|spec.ts
│ │ │ │ └── hero.component.ts&verbar;html&verbar;css&verbar;spec.ts
│ │ │ ├── hero-list
│ │ │ │ └── hero-list.component.ts|html|css|spec.ts
│ │ │ │ └── hero-list.component.ts&verbar;html&verbar;css&verbar;spec.ts
│ │ │ ├── shared
│ │ │ │ └── hero-button.component.ts|html|css|spec.ts
│ │ │ │ └── hero-button.component.ts&verbar;html&verbar;css&verbar;spec.ts
│ │ │ │ └── hero.model.ts
│ │ │ │ └── hero.service.ts|spec.ts
│ │ │ └── heroes.component.ts|html|css|spec.ts
│ │ │ │ └── hero.service.ts&verbar;spec.ts
│ │ │ └── heroes.component.ts&verbar;html&verbar;css&verbar;spec.ts
│ │ │ └── heroes.routes.ts
│ │ ├── shared
│ │ │ └── init-caps.pipe.ts|spec.ts
│ │ │ └── filter-text.component.ts|spec.ts
│ │ │ └── filter-text.service.ts|spec.ts
│ │ │ └── init-caps.pipe.ts&verbar;spec.ts
│ │ │ └── filter-text.component.ts&verbar;spec.ts
│ │ │ └── filter-text.service.ts&verbar;spec.ts
│ │ ├── villains
│ │ │ ├── villain
│ │ │ │ └── …
│ │ │ │ └── &hellip;
│ │ │ ├── villain-list
│ │ │ │ └── …
│ │ │ │ └── &hellip;
│ │ │ ├── shared
│ │ │ │ └── …
│ │ │ └── villains.component.ts|html|css|spec.ts
│ │ │ │ └── &hellip;
│ │ │ └── villains.component.ts&verbar;html&verbar;css&verbar;spec.ts
│ │ │ └── villains.module.ts
│ │ │ └── villains-routing.module.ts
│ │ └── app.component.ts|html|css|spec.ts
│ │ └── app.component.ts&verbar;html&verbar;css&verbar;spec.ts
│ │ └── app.routes.ts
│ └── main.ts
│ └── index.html
│ └── …
└── node_modules/…
└── …
│ └── &hellip;
└── node_modules/&hellip;
└── &hellip;
```
HELPFUL: While components in dedicated folders are widely preferred, another option for small applications is to keep components flat \(not in a dedicated folder\).
@@ -506,15 +506,15 @@ project root
├──├──app
├──├──├── shared
├──├──├──└── shared.module.ts
├──├──├──└── init-caps.pipe.ts|spec.ts
├──├──├──└── filter-text.component.ts|spec.ts
├──├──├──└── filter-text.service.ts|spec.ts
├──├──└── app.component.ts|html|css|spec.ts
├──├──├──└── init-caps.pipe.ts&verbar;spec.ts
├──├──├──└── filter-text.component.ts&verbar;spec.ts
├──├──├──└── filter-text.service.ts&verbar;spec.ts
├──├──└── app.component.ts&verbar;html&verbar;css&verbar;spec.ts
├──├──└── app.module.ts
├──├──└── app-routing.module.ts
├──└── main.ts
├──└── index.html
└── …
└── &hellip;
```
<docs-code-multifile>
@@ -761,7 +761,7 @@ An element may have more than one attribute directive applied.
**Do** be consistent in your choice.
**Why**? <br />
The property associated with `@HostBinding` or the method associated with `@HostListener` can be modified only in a single place —in the directive's class.
The property associated with `@HostBinding` or the method associated with `@HostListener` can be modified only in a single place &mdash;in the directive's class.
If you use the `host` metadata property, you must modify both the property/method declaration in the directive's class and the metadata in the decorator associated with the directive.
<docs-code header="app/shared/validator.directive.ts" path="adev/src/content/examples/styleguide/src/06-03/app/shared/validator.directive.ts"/>
@@ -811,7 +811,7 @@ In this scenario it would be better to provide the service at the component leve
<docs-code header="src/app/treeshaking/service.ts" path="adev/src/content/examples/dependency-injection/src/app/tree-shaking/service.ts"/>
### Use the @Injectable() class decorator
### Use the &commat;Injectable() class decorator
#### Style 07-04
+1 -1
View File
@@ -6,7 +6,7 @@ Keeping your Angular application up-to-date enables you to take advantage of lea
This document contains information and resources to help you keep your Angular applications and libraries up-to-date.
For information about our versioning policy and practices —including support and deprecation practices, as well as the release schedule— see [Angular versioning and releases](reference/releases "Angular versioning and releases").
For information about our versioning policy and practices &mdash;including support and deprecation practices, as well as the release schedule&mdash; see [Angular versioning and releases](reference/releases "Angular versioning and releases").
HELPFUL: If you are currently using AngularJS, see [Upgrading from AngularJS](https://angular.io/guide/upgrade "Upgrading from Angular JS").
*AngularJS* is the name for all v1.x versions of Angular.
@@ -33,7 +33,7 @@ After running this command you can see that the `angular.json` configuration fil
<docs-code language="json">
"server": {
"builder": "@angular-devkit/build-angular:server",
"builder": "&commat;angular-devkit/build-angular:server",
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/my-app/server",
@@ -58,7 +58,7 @@ After running this command you can see that the `angular.json` configuration fil
}
},
"app-shell": {
"builder": "@angular-devkit/build-angular:app-shell",
"builder": "&commat;angular-devkit/build-angular:app-shell",
"defaultConfiguration": "production",
"options": {
"route": "shell"
@@ -28,7 +28,7 @@ The `versionUpdates` is an `Observable` property of `SwUpdate` and emits four ev
### Checking for updates
It's possible to ask the service worker to check if any updates have been deployed to the server.
The service worker checks for updates during initialization and on each navigation request —that is, when the user navigates from a different address to your application.
The service worker checks for updates during initialization and on each navigation request &mdash;that is, when the user navigates from a different address to your application.
However, you might choose to manually check for updates if you have a site that changes frequently or want updates to happen on a schedule.
Do this with the `checkForUpdate()` method:
@@ -7,7 +7,7 @@ This topic describes the properties of the service worker configuration file.
The `ngsw-config.json` JSON configuration file specifies which files and data URLs the Angular service worker should cache and how it should update the cached files and data.
The [Angular CLI](tools/cli) processes this configuration file during `ng build`.
All file paths must begin with `/`, which corresponds to the deployment directory — usually `dist/<project-name>` in CLI projects.
All file paths must begin with `/`, which corresponds to the deployment directory &mdash; usually `dist/<project-name>` in CLI projects.
Unless otherwise commented, patterns use a **limited*** glob format that internally will be converted into regex:
@@ -68,10 +68,10 @@ This field contains an array of asset groups, each of which defines a set of ass
{
"assetGroups": [
{
…
&hellip;
},
{
…
&hellip;
}
]
}
@@ -169,10 +169,10 @@ This field contains an array of data groups, each of which defines a set of data
{
"dataGroups": [
{
…
&hellip;
},
{
…
&hellip;
}
]
}
@@ -197,7 +197,7 @@ export interface DataGroup {
maxSize: number;
maxAge: string;
timeout?: string;
strategy?: 'freshness' | 'performance';
strategy?: 'freshness' &verbar; 'performance';
};
cacheQueryOptions?: {
ignoreSearch?: boolean;
@@ -226,7 +226,7 @@ Only non-mutating requests (GET and HEAD) are cached.
Occasionally APIs change formats in a way that is not backward-compatible.
A new version of the application might not be compatible with the old API format and thus might not be compatible with existing cached resources from that API.
`version` provides a mechanism to indicate that the resources being cached have been updated in a backwards-incompatible way, and that the old cache entries —those from previous versions— should be discarded.
`version` provides a mechanism to indicate that the resources being cached have been updated in a backwards-incompatible way, and that the old cache entries &mdash;those from previous versions&mdash; should be discarded.
`version` is an integer field and defaults to `1`.
@@ -306,7 +306,7 @@ In case you are not familiar, an [opaque response][https://fetch.spec.whatwg.org
One of the characteristics of an opaque response is that the service worker is not allowed to read its status, meaning it can't check if the request was successful or not.
See [Introduction to fetch()][https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types] for more details.
If you are not able to implement CORS — for example, if you don't control the origin — prefer using the `freshness` strategy for resources that result in opaque responses.
If you are not able to implement CORS &mdash; for example, if you don't control the origin &mdash; prefer using the `freshness` strategy for resources that result in opaque responses.
</docs-callout>
@@ -155,7 +155,7 @@ Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100
Last update tick: 1s496u
Last update run: never
Task queue:
* init post-load (update, cleanup)
&ast; init post-load (update, cleanup)
Debug log:
@@ -232,7 +232,7 @@ HELPFUL: This version hash is the "latest manifest hash" listed above. Both clie
Last update tick: 1s496u
Last update run: never
Task queue:
* init post-load (update, cleanup)
&ast; init post-load (update, cleanup)
</docs-code>
@@ -54,7 +54,7 @@ To simulate a network issue, disable network interaction for your application.
In Chrome:
1. Select **Tools** > **Developer Tools** (from the Chrome menu located in the top right corner).
1. Select **Tools** &gt; **Developer Tools** (from the Chrome menu located in the top right corner).
1. Go to the **Network tab**.
1. Select **Offline** in the **Throttling** dropdown menu.
@@ -110,7 +110,7 @@ Make a change to the application, and watch the service worker install the updat
<docs-code language="shell">
ng build
npx http-server -p 8080 -c-1 dist/<project-name>/browser
npx http-server -p 8080 -c-1 dist/&lt;project-name&gt;/browser
</docs-code>
@@ -14,7 +14,7 @@ Invoke push notifications by pushing a message with a valid payload.
See `SwPush` for guidance.
HELPFUL: In Chrome, you can test push notifications without a backend.
Open Devtools -> Application -> Service Workers and use the `Push` input to send a JSON notification payload.
Open Devtools -&gt; Application -&gt; Service Workers and use the `Push` input to send a JSON notification payload.
## Notification click handling
+5 -5
View File
@@ -11,7 +11,7 @@ To add a web worker to an existing project, use the Angular CLI `ng generate` co
<docs-code language="shell">
ng generate web-worker <location>
ng generate web-worker &lt;location&gt;
</docs-code>
@@ -31,8 +31,8 @@ The command performs the following actions.
<docs-code language="typescript" header="src/app/app.worker.ts">
addEventListener('message', ({ data }) => {
const response = `worker response to ${data}`;
addEventListener('message', ({ data }) =&gt; {
const response = `worker response to &dollar;{data}`;
postMessage(response);
});
@@ -45,8 +45,8 @@ The command performs the following actions.
if (typeof Worker !== 'undefined') {
// Create a new
const worker = new Worker(new URL('./app.worker', import.meta.url));
worker.onmessage = ({ data }) => {
console.log(`page got message: ${data}`);
worker.onmessage = ({ data }) =&gt; {
console.log(`page got message: &dollar;{data}`);
};
worker.postMessage('hello');
} else {
@@ -1,5 +1,5 @@
load("//aio/content/examples:examples.bzl", "docs_example")
load("@npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
load("@aio_npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
package(default_visibility = ["//visibility:public"])
@@ -6,28 +6,32 @@
<blockquote>
<!-- #docregion asterisk -->
<div *ngIf="hero" class="name">{{hero.name}}</div>
@if (hero) {<div class="name">{{ hero.name }}</div>}
<!-- #enddocregion asterisk -->
</blockquote>
<p>List of heroes</p>
<ul>
<li *ngFor="let hero of heroes">{{hero.name}}</li>
@for (hero of heroes; track hero) {<li>{{ hero.name }}</li>}
</ul>
<hr>
<h2 id="ngIf">NgIf</h2>
<p *ngIf="true">
Expression is true and ngIf is true.
This paragraph is in the DOM.
</p>
<p *ngIf="false">
Expression is false and ngIf is false.
This paragraph is not in the DOM.
</p>
@if (true) {
<p>
Expression is true and ngIf is true.
This paragraph is in the DOM.
</p>
}
@if (false) {
<p>
Expression is false and ngIf is false.
This paragraph is not in the DOM.
</p>
}
<p [style.display]="'block'">
Expression sets display to "block".
@@ -42,7 +46,7 @@
<p>&lt;ng-template&gt; element</p>
<!-- #docregion ngif-template -->
<ng-template [ngIf]="hero">
<div class="name">{{hero.name}}</div>
<div class="name">{{ hero.name }}</div>
</ng-template>
<!-- #enddocregion ngif-template -->
@@ -57,18 +61,18 @@
<!-- #docregion ngif-ngcontainer -->
<p>
I turned the corner
<ng-container *ngIf="hero">
and saw {{hero.name}}. I waved
</ng-container>
@if (hero) {
and saw {{ hero.name }}. I waved
}
and continued on my way.
</p>
<!-- #enddocregion ngif-ngcontainer -->
<p>
I turned the corner
<span *ngIf="hero">
and saw {{hero.name}}. I waved
</span>
@if (hero) {
<span>and saw {{ hero.name }}. I waved</span>
}
and continued on my way.
</p>
@@ -79,11 +83,15 @@
(<label for="show-sad"><input id="show-sad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
<span *ngFor="let h of heroes">
<span *ngIf="showSad || h.emotion !== 'sad'">
<option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
@for (h of heroes; track h) {
<span>
@if (showSad || h.emotion !== 'sad') {
<span>
<option [ngValue]="h">{{ h.name }} ({{ h.emotion }})</option>
</span>
}
</span>
</span>
}
</select>
<p><em>&lt;select&gt; with &lt;ng-container&gt;</em></p>
@@ -93,11 +101,11 @@
(<label for="showSad"><input id="showSad" type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
<ng-container *ngFor="let h of heroes">
<ng-container *ngIf="showSad || h.emotion !== 'sad'">
<option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
</ng-container>
</ng-container>
@for (h of heroes; track h) {
@if (showSad || h.emotion !== 'sad') {
<option [ngValue]="h">{{ h.name }} ({{ h.emotion }})</option>
}
}
</select>
<!-- #enddocregion select-ngcontainer -->
<br><br>
@@ -110,11 +118,12 @@
<p class="code">&lt;div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"&gt;</p>
<!--#docregion inside-ngfor -->
<div
*ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById"
[class.odd]="odd">
({{i}}) {{hero.name}}
</div>
@for (hero of heroes; track hero; let odd = $odd; let i = $index) {
<div
[class.odd]="odd">
({{ i }}) {{ hero.name }}
</div>
}
<!--#enddocregion inside-ngfor -->
<p class="code">&lt;ng-template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"/&gt;</p>
@@ -122,7 +131,7 @@
<ng-template ngFor let-hero [ngForOf]="heroes"
let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
<div [class.odd]="odd">
({{i}}) {{hero.name}}
({{ i }}) {{ hero.name }}
</div>
</ng-template>
<!--#enddocregion inside-ngfor -->
@@ -134,35 +143,46 @@
<div>Pick your favorite hero</div>
<p>
<label for="hero-{{h}}" *ngFor="let h of heroes">
<input id="hero-{{h}}" type="radio" name="heroes" [(ngModel)]="hero" [value]="h">{{h.name}}
</label>
@for (h of heroes; track h) {
<label for="hero-{{ h }}">
<input id="hero-{{ h }}" type="radio" name="heroes" [(ngModel)]="hero" [value]="h">{{ h.name }}
</label>
}
<label for="none-of-the-above"><input id="none-of-the-above" type="radio" name="heroes" (click)="hero = null">None of the above</label>
</p>
<h4>NgSwitch</h4>
<div [ngSwitch]="hero?.emotion">
<app-happy-hero *ngSwitchCase="'happy'" [hero]="hero!"></app-happy-hero>
<app-sad-hero *ngSwitchCase="'sad'" [hero]="hero!"></app-sad-hero>
<app-confused-hero *ngSwitchCase="'confused'" [hero]="hero!"></app-confused-hero>
<app-unknown-hero *ngSwitchDefault [hero]="hero!"></app-unknown-hero>
<div>
@switch (hero?.emotion) {
@case ('happy') {
<app-happy-hero [hero]="hero!"></app-happy-hero>
} @case ('sad') {
<app-sad-hero [hero]="hero!"></app-sad-hero>
} @case ('confused') {
<app-confused-hero [hero]="hero!"></app-confused-hero>
} @default {
<app-unknown-hero [hero]="hero!"></app-unknown-hero>
}
}
</div>
<h4>NgSwitch with &lt;ng-template&gt;</h4>
<div [ngSwitch]="hero?.emotion">
<ng-template ngSwitchCase="happy">
<app-happy-hero [hero]="hero!"></app-happy-hero>
</ng-template>
<ng-template ngSwitchCase="sad">
<app-sad-hero [hero]="hero!"></app-sad-hero>
</ng-template>
<ng-template ngSwitchCase="confused">
<app-confused-hero [hero]="hero!"></app-confused-hero>
</ng-template >
<ng-template ngSwitchDefault>
<app-unknown-hero [hero]="hero!"></app-unknown-hero>
</ng-template>
<div>
@switch (hero?.emotion) {
@case ('happy') {
<app-happy-hero [hero]="hero!"></app-happy-hero>
}
@case ('sad') {
<app-sad-hero [hero]="hero!"></app-sad-hero>
}
@case ('confused') {
<app-confused-hero [hero]="hero!"></app-confused-hero>
}
@default {
<app-unknown-hero [hero]="hero!"></app-unknown-hero>
}
}
</div>
<hr>
@@ -172,12 +192,12 @@
<!-- #docregion toggle-info -->
<p>
The condition is currently
<span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{condition}}</span>.
<span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{ condition }}</span>.
<button
type="button"
(click)="condition = !condition"
[ngClass] = "{ 'a': condition, 'b': !condition }" >
Toggle condition to {{condition ? 'false' : 'true'}}
[ngClass]="{ 'a': condition, 'b': !condition }" >
Toggle condition to {{ condition ? 'false' : 'true' }}
</button>
</p>
<!-- #enddocregion toggle-info -->
@@ -1,5 +1,5 @@
load("//aio/content/examples:examples.bzl", "docs_example")
load("@npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
load("@aio_npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
package(default_visibility = ["//visibility:public"])
@@ -1,5 +1,5 @@
load("//aio/content/examples:examples.bzl", "docs_example")
load("@npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
load("@aio_npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
package(default_visibility = ["//visibility:public"])
@@ -1,5 +1,5 @@
load("//aio/content/examples:examples.bzl", "docs_example")
load("@npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
load("@aio_npm//@angular/build-tooling/bazel/remote-execution:index.bzl", "ENABLE_NETWORK")
package(default_visibility = ["//visibility:public"])
@@ -196,7 +196,7 @@ HELPFUL: Some additional notes on using styles within [`state`](api/animations/s
<docs-code language="typescript">
transition( 'on => off, off => void' )
transition( 'on =&gt; off, off =&gt; void' )
</docs-code>
@@ -227,7 +227,7 @@ Then, you can bind the trigger to a template expression using standard Angular p
<docs-code language="typescript">
<div [@triggerName]="expression">…</div>;
&lt;div [&commat;triggerName]="expression"&gt;&hellip;&lt;/div&gt;;
</docs-code>
@@ -113,7 +113,7 @@ Adding these styles to the views animates the containers in place and prevents o
Use the `query()` method to find and animate elements within the current host component.
The `query(":enter")` statement returns the view that is being inserted, and `query(":leave")` returns the view that is being removed.
Assume that you are routing from the *Home => About*.
Assume that you are routing from the *Home =&gt; About*.
<docs-code header="src/app/animations.ts (excerpt)" path="adev/src/content/examples/animations/src/app/animations.ts" visibleRegion="query"/>
@@ -85,8 +85,8 @@ These aliases are used by several animation functions.
<docs-code hideCopy language="typescript">
transition ( ':enter', [ … ] ); // alias for void => *
transition ( ':leave', [ … ] ); // alias for * => void
transition ( ':enter', [ &hellip; ] ); // alias for void =&gt; &ast;
transition ( ':leave', [ &hellip; ] ); // alias for * =&gt; void
</docs-code>
@@ -1,109 +0,0 @@
# Function-based outputs
The `output()` function declares an output in a directive or component.
Outputs allow you to emit values to parent components.
HELPFUL: The `output()` function is currently in [developer preview](/guide/releases#developer-preview).
<docs-code language="ts" highlight="[[5], [8]]">
import {Component, output} from '@angular/core';
@Component({...})
export class MyComp {
onNameChange = output<string>() // OutputEmitterRef<string>
setNewName(newName: string) {
this.onNameChange.emit(newName);
}
}
</docs-code>
An output is automatically recognized by Angular whenever you use the `output` function as an initializer of a class member.
Parent components can listen to outputs in templates by using the event binding syntax.
```html
<my-comp (onNameChange)="showNewName($event)" />
```
## Aliasing an output
Angular uses the class member name as the name of the output.
You can alias outputs to change their public name to be different.
```typescript
class MyComp {
onNameChange = output({alias: 'ngxNameChange'});
}
```
This allows users to bind to your output using `(ngxNameChange)`, while inside your component you can access the output emitter using `this.onNameChange`.
## Subscribing programmatically
Consumers may create your component dynamically with a reference to a `ComponentRef`.
In those cases, parents can subscribe to outputs by directly accessing the property of type `OutputRef`.
```ts
const myComp = viewContainerRef.createComponent(...);
myComp.instance.onNameChange.subscribe(newName => {
console.log(newName);
});
```
Angular will automatically clean up the subscription when `myComp` is destroyed.
Alternatively, an object with a function to explicitly unsubscribe earlier is returned.
## Using RxJS observables as source
In some cases, you may want to emit output values based on RxJS observables.
Angular provides a way to use RxJS observables as source for outputs.
The `outputFromObservable` function is a compiler primitive, similar to the `output()` function, and declares outputs that are driven by RxJS observables.
<docs-code language="ts" highlight="[7]">
import {Directive} from '@angular/core';
import {outputFromObservable} from '@angular/core/rxjs-interop';
@Directive(...)
class MyDir {
nameChange$ = this.dataService.get(); // Observable<Data>
nameChange = outputFromObservable(this.nameChange$);
}
</docs-code>
Angular will forward subscriptions to the observable, but will stop forwarding values when the owning directive is destroyed.
In the example above, if `MyDir` is destroyed, `nameChange` will no longer emit values.
HELPFUL: Most of the time, using `output()` is sufficient and you can emit values imperatively.
## Converting an output to an observable
You can subscribe to outputs by calling `.subscribe` method on `OutputRef`.
In other cases, Angular provides a helper function that converts an `OutputRef` to an observable.
<docs-code language="ts" highlight="[11]">
import {outputToObservable} from '@angular/core/rxjs-interop';
@Component(...)
class MyComp {
onNameChange = output<string>();
}
// Instance reference to `MyComp`.
const myComp: MyComp;
outputToObservable(this.myComp.instance.onNameChange) // Observable<string>
.pipe(...)
.subscribe(...);
</docs-code>
## Why you should use `output()` over decorator-based `@Output()`?
The `output()` function provides numerous benefits over decorator-based `@Output` and `EventEmitter`:
1. Simpler mental model and API:
<br/>• No concept of error channel, completion channels, or other APIs from RxJS.
<br/>• Outputs are simple emitters. You can emit values using the `.emit` function.
2. More accurate types.
<br/>• `OutputEmitterRef.emit(value)` is now correctly typed, while `EventEmitter` has broken types and can cause runtime errors.
+1 -3
View File
@@ -56,9 +56,7 @@ Note: Certain triggers may require the presence of either a `@placeholder` or a
### `@loading`
The `@loading` block is an optional block that allows you to declare content that will be shown during the loading of any deferred dependencies. Its dependences are eagerly loaded (similar to `@placeholder`).
For example, you could show a loading spinner. Once loading has been triggered, the `@loading` block replaces the `@placeholder` block.
The `@loading` block is an optional block that allows you to declare content that will be shown during the loading of any deferred dependencies. For example, you could show a loading spinner. Similar to `@placeholder`, the dependencies of the `@loading` block are eagerly loaded.
The `@loading` block accepts two optional parameters to specify the `minimum` amount of time that this placeholder should be shown and amount of time to wait `after` loading begins before showing the loading template. `minimum` and `after` parameters are specified in time increments of milliseconds (ms) or seconds (s). Just like `@placeholder`, these parameters exist to prevent fast flickering of content in the case that the deferred dependencies are fetched quickly. Both the `minimum` and `after` timers for the `@loading` block begins immediately after the loading has been triggered.
@@ -30,7 +30,7 @@ The `EnvironmentInjector` can be configured in one of two ways by using:
* The `@Injectable()` `providedIn` property to refer to `root` or `platform`
* The `ApplicationConfig` `providers` array
<docs-callout title="Tree-shaking and @Injectable()">
<docs-callout title="Tree-shaking and &commat;Injectable()">
Using the `@Injectable()` `providedIn` property is preferable to using the `ApplicationConfig` `providers` array. With `@Injectable()` `providedIn`, optimization tools can perform tree-shaking, which removes services that your application isn't using. This results in smaller bundle sizes.
@@ -46,7 +46,7 @@ Provide services using `providedIn` of `@Injectable()` as follows:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root' // <--provides this service in the root EnvironmentInjector
providedIn: 'root' // &lt;--provides this service in the root EnvironmentInjector
})
export class ItemService {
name = 'telephone';
@@ -134,7 +134,7 @@ For example, the following `TestComponent` configures the `ElementInjector` by p
<docs-code language="typescript" highlight="[3]">
@Component({
…
&hellip;
providers: [{ provide: ItemService, useValue: { name: 'lamp' } }]
})
export class TestComponent
@@ -335,11 +335,11 @@ The following is an example of how the `<app-root>` and `<app-child>` view trees
<#VIEW>
<app-child>
<#VIEW>
…content goes here…
&hellip;content goes here&hellip;
</#VIEW>
</app-child>
</#VIEW>
</app-root>
<#VIEW>
<app-root>
</docs-code>
Understanding the idea of the `<#VIEW>` demarcation is especially significant when you configure services in the component class.
@@ -387,24 +387,24 @@ The most basic rendered view would look like nested HTML elements such as the fo
<docs-code language="html">
<app-root> <!-- AppComponent selector -->
<app-child> <!-- ChildComponent selector -->
</app-child>
</app-root>
&lt;app-root&gt; &lt;!-- AppComponent selector --&gt;
&lt;app-child&gt; &lt;!-- ChildComponent selector --&gt;
&lt;/app-child&gt;
&lt;/app-root&gt;
</docs-code>
However, behind the scenes, Angular uses a logical view representation as follows when resolving injection requests:
<docs-code language="html">
<app-root> <!-- AppComponent selector -->
<#VIEW>
<app-child> <!-- ChildComponent selector -->
<#VIEW>
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-root&gt; &lt;!-- AppComponent selector --&gt;
&lt;#VIEW&gt;
&lt;app-child&gt; &lt;!-- ChildComponent selector --&gt;
&lt;#VIEW&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
The `<#VIEW>` here represents an instance of a template.
@@ -435,16 +435,16 @@ Emoji from FlowerService: &#x1F33A;
In the logical tree, this would be represented as follows:
<docs-code language="html" highlight="[[1],[2],[4]]">
<app-root @ApplicationConfig
@Inject(FlowerService) flower=>"&#x1F33A;">
<#VIEW>
<p>Emoji from FlowerService: {{flower.emoji}} (&#x1F33A;)</p>
<app-child>
<#VIEW>
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-root @ApplicationConfig
&commat;Inject(FlowerService) flower=&gt;"&#x1F33A;"&gt;
&lt;#VIEW&gt;
&lt;p&gt;Emoji from FlowerService: {{flower.emoji}} (&#x1F33A;)&lt;/p&gt;
&lt;app-child&gt;
&lt;#VIEW&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -507,19 +507,19 @@ In the logical tree, this is represented as follows:
<docs-code language="html">
<app-root @ApplicationConfig
@Inject(FlowerService) flower=>"&#x1F33A;">
<#VIEW>
<p>Emoji from FlowerService: {{flower.emoji}} (&#x1F33A;)</p>
<app-child @Provide(FlowerService="&#x1F33B;")
@Inject(FlowerService)=>"&#x1F33B;"> <!-- search ends here -->
<#VIEW> <!-- search starts here -->
<h2>Child Component</h2>
<p>Emoji from FlowerService: {{flower.emoji}} (&#x1F33B;)</p>
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-root @ApplicationConfig
&commat;Inject(FlowerService) flower=&gt;"&#x1F33A;"&gt;
&lt;#VIEW&gt;
&lt;p&gt;Emoji from FlowerService: {{flower.emoji}} (&#x1F33A;)&lt;/p&gt;
&lt;app-child &commat;Provide(FlowerService="&#x1F33B;")
&commat;Inject(FlowerService)=&gt;"&#x1F33B;"&gt; &lt;!-- search ends here --&gt;
&lt;#VIEW&gt; &lt;!-- search starts here --&gt;
&lt;h2&gt;Child Component&lt;/h2&gt;
&lt;p&gt;Emoji from FlowerService: {{flower.emoji}} (&#x1F33B;)&lt;/p&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -613,18 +613,18 @@ The logic tree for this example of `viewProviders` is as follows:
<docs-code language="html">
<app-root @ApplicationConfig
@Inject(AnimalService) animal=>"&#x1F433;">
<#VIEW>
<app-child>
<#VIEW @Provide(AnimalService="&#x1F436;")
@Inject(AnimalService=>"&#x1F436;")>
<!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
<p>Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)</p>
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-root @ApplicationConfig
&commat;Inject(AnimalService) animal=&gt;"&#x1F433;"&gt;
&lt;#VIEW&gt;
&lt;app-child&gt;
&lt;#VIEW &commat;Provide(AnimalService="&#x1F436;")
&commat;Inject(AnimalService=&gt;"&#x1F436;")&gt;
&lt;!-- ^^using viewProviders means AnimalService is available in &lt;#VIEW&gt;--&gt;
&lt;p&gt;Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)&lt;/p&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -715,31 +715,31 @@ The `AnimalService` in the logical tree would look like this:
<docs-code language="html">
<app-root @ApplicationConfig
@Inject(AnimalService) animal=>"&#x1F433;">
<#VIEW>
<app-child>
<#VIEW @Provide(AnimalService="&#x1F436;")
@Inject(AnimalService=>"&#x1F436;")>
<!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
<p>Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)</p>
&lt;app-root @ApplicationConfig
&commat;Inject(AnimalService) animal=&gt;"&#x1F433;"&gt;
&lt;#VIEW&gt;
&lt;app-child&gt;
&lt;#VIEW &commat;Provide(AnimalService="&#x1F436;")
&commat;Inject(AnimalService=&gt;"&#x1F436;")&gt;
&lt;!-- ^^using viewProviders means AnimalService is available in &lt;#VIEW&gt;--&gt;
&lt;p&gt;Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)&lt;/p&gt;
<div class="container">
<h3>Content projection</h3>
<app-inspector @Inject(AnimalService) animal=>"&#x1F433;">
<p>Emoji from AnimalService: {{animal.emoji}} (&#x1F433;)</p>
</app-inspector>
</div>
&lt;div class="container"&gt;
&lt;h3&gt;Content projection&lt;/h3&gt;
&lt;app-inspector &commat;Inject(AnimalService) animal=&gt;"&#x1F433;"&gt;
&lt;p&gt;Emoji from AnimalService: {{animal.emoji}} (&#x1F433;)&lt;/p&gt;
&lt;/app-inspector&gt;
&lt;/div&gt;
<app-inspector>
<#VIEW @Inject(AnimalService) animal=>"&#x1F436;">
<p>Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)</p>
</#VIEW>
</app-inspector>
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-inspector&gt;
&lt;#VIEW &commat;Inject(AnimalService) animal=&gt;"&#x1F436;"&gt;
&lt;p&gt;Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)&lt;/p&gt;
&lt;/#VIEW&gt;
&lt;/app-inspector&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -773,16 +773,16 @@ In a logical tree, this same idea might look like this:
<docs-code language="html">
<app-root @ApplicationConfig
@Inject(FlowerService) flower=>"&#x1F33A;">
<#VIEW>
<app-child @Provide(FlowerService="&#x1F33B;")>
<#VIEW @Inject(FlowerService, SkipSelf)=>"&#x1F33A;">
<!-- With SkipSelf, the injector looks to the next injector up the tree (app-root) -->
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-root @ApplicationConfig
&commat;Inject(FlowerService) flower=&gt;"&#x1F33A;"&gt;
&lt;#VIEW&gt;
&lt;app-child &commat;Provide(FlowerService="&#x1F33B;")&gt;
&lt;#VIEW &commat;Inject(FlowerService, SkipSelf)=&gt;"&#x1F33A;"&gt;
&lt;!-- With SkipSelf, the injector looks to the next injector up the tree (app-root) --&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -794,15 +794,15 @@ Here's the idea in the logical tree:
<docs-code language="html">
<app-root @ApplicationConfig
@Inject(FlowerService) flower=>"&#x1F33A;">
<#VIEW> <!-- end search here with null-->
<app-child @Provide(FlowerService="&#x1F33B;")> <!-- start search here -->
<#VIEW @Inject(FlowerService, @SkipSelf, @Host, @Optional)=>null>
</#VIEW>
</app-parent>
</#VIEW>
</app-root>
&lt;app-root @ApplicationConfig
&commat;Inject(FlowerService) flower=&gt;"&#x1F33A;"&gt;
&lt;#VIEW&gt; &lt;!-- end search here with null--&gt;
&lt;app-child &commat;Provide(FlowerService="&#x1F33B;")&gt; &lt;!-- start search here --&gt;
&lt;#VIEW &commat;Inject(FlowerService, &commat;SkipSelf, &commat;Host, &commat;Optional)=&gt;null&gt;
&lt;/#VIEW&gt;
&lt;/app-parent&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -820,7 +820,7 @@ Instead, the injector will begin at the `<app-root>` `ElementInjector`.
@Component({
standalone: true,
selector: 'app-child',
…
&hellip;
viewProviders: [
{ provide: AnimalService, useValue: { emoji: '&#x1F436;' } },
],
@@ -831,17 +831,17 @@ The logical tree looks like this with `@SkipSelf()` in `<app-child>`:
<docs-code language="html">
<app-root @ApplicationConfig
@Inject(AnimalService=>"&#x1F433;")>
<#VIEW><!-- search begins here -->
<app-child>
<#VIEW @Provide(AnimalService="&#x1F436;")
@Inject(AnimalService, SkipSelf=>"&#x1F433;")>
<!--Add @SkipSelf -->
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-root @ApplicationConfig
&commat;Inject(AnimalService=&gt;"&#x1F433;")&gt;
&lt;#VIEW&gt;&lt;!-- search begins here --&gt;
&lt;app-child&gt;
&lt;#VIEW &commat;Provide(AnimalService="&#x1F436;")
&commat;Inject(AnimalService, SkipSelf=&gt;"&#x1F433;")&gt;
&lt;!--Add &commat;SkipSelf --&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -857,7 +857,7 @@ You can also see `@Host()` in the constructor:
@Component({
standalone: true
selector: 'app-child',
…
&hellip;
viewProviders: [
{ provide: AnimalService, useValue: { emoji: '&#x1F436;' } },
]
@@ -871,16 +871,16 @@ export class ChildComponent {
<docs-code language="html">
<app-root @ApplicationConfig
@Inject(AnimalService=>"&#x1F433;")>
<#VIEW>
<app-child>
<#VIEW @Provide(AnimalService="&#x1F436;")
@Inject(AnimalService, @Host=>"&#x1F436;")> <!-- @Host stops search here -->
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-root @ApplicationConfig
&commat;Inject(AnimalService=&gt;"&#x1F433;")&gt;
&lt;#VIEW&gt;
&lt;app-child&gt;
&lt;#VIEW &commat;Provide(AnimalService="&#x1F436;")
&commat;Inject(AnimalService, &commat;Host=&gt;"&#x1F436;")&gt; &lt;!-- &commat;Host stops search here --&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -915,7 +915,7 @@ export class ChildComponent {
and how `@Host()` works.
-->
When `@Host()` and `@SkipSelf()` were applied to the `FlowerService`, which is in the `providers` array, the result was `null` because `@SkipSelf()` starts its search in the `<app-child>` injector, but `@Host()` stops searching at `<#VIEW>` —where there is no `FlowerService`
When `@Host()` and `@SkipSelf()` were applied to the `FlowerService`, which is in the `providers` array, the result was `null` because `@SkipSelf()` starts its search in the `<app-child>` injector, but `@Host()` stops searching at `<#VIEW>` &mdash;where there is no `FlowerService`
In the logical tree, you can see that the `FlowerService` is visible in `<app-child>`, not its `<#VIEW>`.
However, the `AnimalService`, which is provided in the `AppComponent` `viewProviders` array, is visible.
@@ -924,19 +924,19 @@ The logical tree representation shows why this is:
<docs-code language="html">
<app-root @ApplicationConfig
@Inject(AnimalService=>"&#x1F433;")>
<#VIEW @Provide(AnimalService="&#x1F994;")
@Inject(AnimalService, @Optional)=>"&#x1F994;">
<!-- ^^@SkipSelf() starts here, @Host() stops here^^ -->
<app-child>
<#VIEW @Provide(AnimalService="&#x1F436;")
@Inject(AnimalService, @SkipSelf, @Host, @Optional)=>"&#x1F994;">
<!-- Add @SkipSelf ^^-->
</#VIEW>
</app-child>
</#VIEW>
</app-root>
&lt;app-root @ApplicationConfig
&commat;Inject(AnimalService=&gt;"&#x1F433;")&gt;
&lt;#VIEW &commat;Provide(AnimalService="&#x1F994;")
&commat;Inject(AnimalService, &commat;Optional)=&gt;"&#x1F994;"&gt;
&lt;!-- ^^&commat;SkipSelf() starts here, &commat;Host() stops here^^ --&gt;
&lt;app-child&gt;
&lt;#VIEW &commat;Provide(AnimalService="&#x1F436;")
&commat;Inject(AnimalService, &commat;SkipSelf, &commat;Host, &commat;Optional)=&gt;"&#x1F994;"&gt;
&lt;!-- Add &commat;SkipSelf ^^--&gt;
&lt;/#VIEW&gt;
&lt;/app-child&gt;
&lt;/#VIEW&gt;
&lt;/app-root&gt;
</docs-code>
@@ -25,7 +25,7 @@ This component contains a body and can contain an optional header.
<docs-code language="html">
<lib-card>;
<lib-header>…</lib-header>;
<lib-header>&hellip;</lib-header>;
</lib-card>;
</docs-code>
@@ -35,13 +35,13 @@ In a likely implementation, the `<lib-card>` component uses `@ContentChild()` or
<docs-code language="typescript" highlight="[12]">
@Component({
selector: 'lib-header',
…,
&hellip;,
})
class LibHeaderComponent {}
@Component({
selector: 'lib-card',
…,
&hellip;,
})
class LibCardComponent {
@ContentChild(LibHeaderComponent) header: LibHeaderComponent|null = null;
@@ -108,13 +108,13 @@ abstract class LibHeaderToken {}
providers: [
{provide: LibHeaderToken, useExisting: LibHeaderComponent}
]
…,
&hellip;,
})
class LibHeaderComponent extends LibHeaderToken {}
@Component({
selector: 'lib-card',
…,
&hellip;,
})
class LibCardComponent {
@ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
@@ -156,7 +156,7 @@ abstract class LibHeaderToken {
providers: [
{provide: LibHeaderToken, useExisting: LibHeaderComponent}
]
…,
&hellip;,
})
class LibHeaderComponent extends LibHeaderToken {
doSomething(): void {
@@ -166,7 +166,7 @@ class LibHeaderComponent extends LibHeaderToken {
@Component({
selector: 'lib-card',
…,
&hellip;,
})
class LibCardComponent implement AfterContentInit {
@ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
@@ -130,7 +130,7 @@ When you write your own structural directives, use the following syntax:
<docs-code hideCopy language="typescript">
*:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )*"
&ast;:prefix="( :let &verbar; :expression ) (';' &verbar; ',')? ( :let &verbar; :as &verbar; :keyExp )&ast;"
</docs-code>
+14 -14
View File
@@ -31,7 +31,7 @@ After you register your configured class with the browser's custom-element regis
<docs-code language="html">
<my-popup message="Use Angular!"></my-popup>
&lt;my-popup message="Use Angular!"&gt;&lt;/my-popup&gt;
</docs-code>
@@ -73,7 +73,7 @@ For more information, see Web Component documentation for [Creating custom event
Previously, when you wanted to add a component to an application at runtime, you had to define a _dynamic component_, and then you would have to load it, attach it to an element in the DOM, and wire up all of the dependencies, change detection, and event handling.
Using an Angular custom element makes the process simpler and more transparent, by providing all the infrastructure and framework automatically —all you have to do is define the kind of event handling you want.
Using an Angular custom element makes the process simpler and more transparent, by providing all the infrastructure and framework automatically &mdash;all you have to do is define the kind of event handling you want.
\(You do still have to exclude the component from compilation, if you are not going to use it in your application.\)
The following Popup Service example application defines a component that you can either load dynamically or convert to a custom element.
@@ -111,9 +111,9 @@ Assume you create a `my-dialog` custom element based on the following component:
<docs-code language="typescript">
@Component(…)
&commat;Component(&hellip;)
class MyDialog {
@Input() content: string;
&commat;Input() content: string;
}
</docs-code>
@@ -123,10 +123,10 @@ For that, use the `NgElement` and `WithProperties` types \(both exported from `@
<docs-code language="typescript">
const aDialog = document.createElement('my-dialog') as NgElement & WithProperties<{content: string}>;
const aDialog = document.createElement('my-dialog') as NgElement &amp; WithProperties&lt;{content: string}&gt;;
aDialog.content = 'Hello, world!';
aDialog.content = 123; // <-- ERROR: TypeScript knows this should be a string.
aDialog.body = 'News'; // <-- ERROR: TypeScript knows there is no `body` property on `aDialog`.
aDialog.content = 123; // &lt;-- ERROR: TypeScript knows this should be a string.
aDialog.body = 'News'; // &lt;-- ERROR: TypeScript knows there is no `body` property on `aDialog`.
</docs-code>
@@ -139,9 +139,9 @@ An alternative way, that only requires defining each custom element's type once,
declare global {
interface HTMLElementTagNameMap {
'my-dialog': NgElement & WithProperties<{content: string}>;
'my-other-element': NgElement & WithProperties<{foo: 'bar'}>;
…
'my-dialog': NgElement &amp; WithProperties&lt;{content: string}&gt;;
'my-other-element': NgElement &amp; WithProperties&lt;{foo: 'bar'}&gt;;
&hellip;
}
}
@@ -151,9 +151,9 @@ Now, TypeScript can infer the correct type the same way it does for built-in ele
<docs-code language="typescript">
document.createElement('div') //--> HTMLDivElement (built-in element)
document.querySelector('foo') //--> Element (unknown element)
document.createElement('my-dialog') //--> NgElement & WithProperties<{content: string}> (custom element)
document.querySelector('my-other-element') //--> NgElement & WithProperties<{foo: 'bar'}> (custom element)
document.createElement('div') //--&gt; HTMLDivElement (built-in element)
document.querySelector('foo') //--&gt; Element (unknown element)
document.createElement('my-dialog') //--&gt; NgElement &amp; WithProperties&lt;{content: string}&gt; (custom element)
document.querySelector('my-other-element') //--&gt; NgElement &amp; WithProperties&lt;{foo: 'bar'}&gt; (custom element)
</docs-code>
@@ -42,7 +42,7 @@ The following code from the example shows the setup in the root module.
## Create a form object model
A dynamic form requires an object model that can describe all scenarios needed by the form functionality.
The example hero-application form is a set of questions — that is, each control in the form must ask a question and accept an answer.
The example hero-application form is a set of questions &mdash; that is, each control in the form must ask a question and accept an answer.
The data model for this type of form must represent a question.
The example includes the `DynamicFormQuestionComponent`, which defines a question as the fundamental object in the model.
@@ -61,11 +61,11 @@ The same built-in validators that are available as attributes in template-driven
For a full list of built-in validators, see the [Validators](api/forms/Validators) API reference.
To update the actor form to be a reactive form, use some of the same
built-in validators —this time, in function form, as in the following example.
built-in validators &mdash;this time, in function form, as in the following example.
<docs-code header="reactive/actor-form-reactive.component.ts (validator functions)" path="adev/src/content/examples/form-validation/src/app/reactive/actor-form-reactive.component.1.ts" visibleRegion="form-group"/>
In this example, the `name` control sets up two built-in validators —`Validators.required` and `Validators.minLength(4)`— and one custom validator, `forbiddenNameValidator`.
In this example, the `name` control sets up two built-in validators &mdash;`Validators.required` and `Validators.minLength(4)`&mdash; and one custom validator, `forbiddenNameValidator`.
All of these validators are synchronous, so they are passed as the second argument.
Notice that you can support multiple validators by passing the functions in as an array.
@@ -129,7 +129,7 @@ For example:
<docs-code header="template/actor-form-template.component.html (forbidden-name-input)" path="adev/src/content/examples/form-validation/src/app/template/actor-form-template.component.html" visibleRegion="name-input"/>
HELPFUL: Notice that the custom validation directive is instantiated with `useExisting` rather than `useClass`.
The registered validator must be *this instance* of the `ForbiddenValidatorDirective` —the instance in the form with its `forbiddenName` property bound to "bob".
The registered validator must be *this instance* of the `ForbiddenValidatorDirective` &mdash;the instance in the form with its `forbiddenName` property bound to "bob".
If you were to replace `useExisting` with `useClass`, then you'd be registering a new class instance, one that doesn't have a `forbiddenName`.
@@ -253,8 +253,8 @@ The following example shows how to achieve this in a template-driven form.
<docs-code language="html">
<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator>
<app-spinner *ngIf="model.pending"></app-spinner>
&lt;input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator&gt;
&lt;app-spinner *ngIf="model.pending"&gt;&lt;/app-spinner&gt;
</docs-code>
+5 -5
View File
@@ -110,7 +110,7 @@ The view-to-model diagram shows how data flows when an input field's value is ch
```mermaid
flowchart TB
U{User}
I("<input>")
I("&lt;input&gt;")
CVA(ControlValueAccessor)
FC(FormControl)
O(Observers)
@@ -130,14 +130,14 @@ The model-to-view diagram shows how a programmatic change to the model is propag
```mermaid
flowchart TB
U{User}
I(<input>)
I(&lt;input&gt;)
CVA(ControlValueAccessor)
FC(FormControl)
O(Observers)
U-->|"Calls setValue() on the FormControl"|FC
FC-->|Notifies the ControlValueAccessor|CVA
FC-.->|Fires a 'valueChanges' event to observers|O
CVA-->|"Updates the value of the <input>"|I
CVA-->|"Updates the value of the &lt;input&gt;"|I
```
### Data flow in template-driven forms
@@ -157,7 +157,7 @@ The view-to-model diagram shows how data flows when an input field's value is ch
```mermaid
flowchart TB
U{User}
I(<input>)
I(&lt;input&gt;)
CVA(ControlValueAccessor)
FC(FormControl)
M(NgModel)
@@ -207,7 +207,7 @@ flowchart TB
FC2(FormControl)
O(Observers)
CVA(ControlValueAccessor)
I("<input>")
I("&lt;input&gt;")
FC2-.->|Fires a 'valueChanges' event to observers|O
O-->|ControlValueAccessor receives valueChanges event|CVA
CVA-->|Sets the value in the control|I
@@ -10,7 +10,7 @@ Angular supports two design approaches for interactive forms. Template-driven fo
Template-driven forms are a great choice for small or simple forms, while reactive forms are more scalable and suitable for complex forms. For a comparison of the two approaches, see [Choosing an approach](guide/forms#choosing-an-approach)
</docs-callout>
You can build almost any kind of form with an Angular template —login forms, contact forms, and pretty much any business form.
You can build almost any kind of form with an Angular template &mdash;login forms, contact forms, and pretty much any business form.
You can lay out the controls creatively and bind them to the data in your object model.
You can specify validation rules and display validation errors, conditionally allow input from specific controls, trigger built-in visual feedback, and much more.
@@ -339,11 +339,11 @@ You will bind the form property that indicates its overall validity to the **Sub
</docs-step>
<docs-step title="Run the application">
Notice that the button is enabled —although it doesn't do anything useful yet.
Notice that the button is enabled &mdash;although it doesn't do anything useful yet.
</docs-step>
<docs-step title="Delete the Name value">
This violates the "required" rule, so it displays the error message —and notice that it also disables the **Submit** button.
This violates the "required" rule, so it displays the error message &mdash;and notice that it also disables the **Submit** button.
You didn't have to explicitly wire the button's enabled state to the form's validity.
The `FormsModule` did this automatically when you defined a template reference variable on the enhanced form element, then referred to that variable in the button control.
+8 -8
View File
@@ -83,11 +83,11 @@ HELPFUL: Prefer using [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/C
### `withXsrfConfiguration(...)`
Including this option allows for customization of `HttpClient`'s built-in XSRF security functionality. See the [security guide](best-practices/security) for more information.
Including this option allows for customization of `HttpClient`'s built-in XSRF security functionality. See the [security guide](guide/http/security) for more information.
### `withNoXsrfProtection()`
Including this option disables `HttpClient`'s built-in XSRF security functionality. See the [security guide](best-practices/security) for more information.
Including this option disables `HttpClient`'s built-in XSRF security functionality. See the [security guide](guide/http/security) for more information.
## `HttpClientModule`-based configuration
@@ -95,12 +95,12 @@ Some applications may configure `HttpClient` using the older API based on NgModu
This table lists the NgModules available from `@angular/common/http` and how they relate to the provider configuration functions above.
| **NgModule** | `provideHttpClient()` equivalent |
| --------------------------------------- | --------------------------------------------- |
| `HttpClientModule` | `provideHttpClient(withInterceptorsFromDi())` |
| `HttpClientJsonpModule` | `withJsonpSupport()` |
| `HttpClientXsrfModule.withOptions(...)` | `withXsrfConfiguration(...)` |
| `HttpClientXsrfModule.disable()` | `withNoXsrfProtection()` |
| **NgModule** | `provideHttpClient()` equivalent |
| - | - |
| `HttpClientModule` | `provideHttpClient(withInterceptorsFromDi())` |
| `HttpClientJsonpModule` | `withJsonpSupport()` |
| `HttpClientXsrfModule.withOptions(...)` | `withXsrfConfiguration(...)` |
| `HttpClientXsrfModule.disable()` | `withNoXsrfProtection()` |
<docs-callout important title="Use caution when using HttpClientModule in multiple injectors">
When `HttpClientModule` is present in multiple injectors, the behavior of interceptors is poorly defined and depends on the exact options and provider/import ordering.
+1 -3
View File
@@ -8,13 +8,12 @@ At the end, tests can verify that the app made no unexpected requests.
## Setup for testing
To begin testing usage of `HttpClient`, configure `TestBed` and include `provideHttpClient()` and `provideHttpClientTesting` in your test's setup. This configures `HttpClient` to use a test backend instead of the real network. It also provides `HttpTestingController`, which you'll use to interact with the test backend, set expectations about which requests have been made, and flush responses to those requests. `HttpTestingController` can be injected from `TestBed` once configured.
To begin testing usage of `HttpClient`, configure `TestBed` and include `provideHttpClientTesting` in your test's setup. This configures `HttpClient` to use a test backend instead of the real network. It also provides `HttpTestingController`, which you'll use to interact with the test backend, set expectations about which requests have been made, and flush responses to those requests. `HttpTestingController` can be injected from `TestBed` once configured.
<docs-code language="ts">
TestBed.configureTestingModule({
providers: [
// ... other test providers
provideHttpClient(),
provideHttpClientTesting(),
],
});
@@ -32,7 +31,6 @@ For example, you can write a test that expects a GET request to occur and provid
TestBed.configureTestingModule({
providers: [
ConfigService,
provideHttpClient(),
provideHttpClientTesting(),
],
});
@@ -18,7 +18,7 @@ To display the current date in the format for the current locale, use the follow
<docs-code language="typescript">
{{ today | date }}
{{ today &verbar; date }}
</docs-code>
@@ -32,7 +32,7 @@ To force the currency to use American English \(`en-US`\), use the following for
<docs-code language="typescript">
{{ amount | currency : 'en-US' }}
{{ amount &verbar; currency : 'en-US' }}
</docs-code>
@@ -32,7 +32,7 @@ The following example defines the `introductionHeader` custom ID for a variable.
<docs-code language="typescript">
variableText1 = $localize `:@@introductionHeader:Hello i18n!`;
variableText1 = &dollar;localize `:&commat;&commat;introductionHeader:Hello i18n!`;
</docs-code>
@@ -58,7 +58,7 @@ The following example defines the `introductionHeader` custom ID and description
<docs-code language="typescript">
variableText2 = $localize `:An introduction header for this sample@@introductionHeader:Hello i18n!`;
variableText2 = &dollar;localize `:An introduction header for this sample&commat;&commat;introductionHeader:Hello i18n!`;
</docs-code>
@@ -72,7 +72,7 @@ The following example defines the `introductionHeader` custom ID for a variable.
<docs-code language="typescript">
variableText3 = $localize `:site header|An introduction header for this sample@@introductionHeader:Hello i18n!`;
variableText3 = &dollar;localize `:site header|An introduction header for this sample&commat;&commat;introductionHeader:Hello i18n!`;
</docs-code>
+10 -10
View File
@@ -11,7 +11,7 @@ To prepare your project for translation, complete the following actions.
In a component template, the i18n metadata is the value of the `i18n` attribute.
<docs-code language="html">
<element i18n="{i18n_metadata}">{string_to_translate}</element>
&lt;element i18n="{i18n_metadata}"&gt;{string_to_translate}&lt;/element&gt;
</docs-code>
Use the `i18n` attribute to mark a static text message in your component templates for translation.
@@ -44,7 +44,7 @@ The following example shows the `<ng-container>` element transformed into a non-
In a component template, the i18n metadata is the value of the `i18n-{attribute_name}` attribute.
<docs-code language="html">
<element i18n-{attribute_name}="{i18n_metadata}" {attribute_name}="{attribute_value}" />
&lt;element i18n-{attribute_name}="{i18n_metadata}" {attribute_name}="{attribute_value}" /&gt;
</docs-code>
The attributes of HTML elements include text that should be translated along with the rest of the displayed text in the component template.
@@ -55,7 +55,7 @@ Use the following syntax to assign a meaning, description, and custom ID.
<!--todo: replace with docs-code -->
<docs-code language="html">
i18n-{attribute_name}="{meaning}|{description}@@{id}"
i18n-{attribute_name}="{meaning}|{description}&commat;&commat;{id}"
</docs-code>
### `i18n-title` example
@@ -82,7 +82,7 @@ Use the [`$localize`][ApiLocalizeInitLocalize] tagged message string to mark a s
<!--todo: replace with docs-code -->
<docs-code language="typescript">
$localize `string_to_translate`;
&dollar;localize `string_to_translate`;
</docs-code>
The i18n metadata is surrounded by colon \(`:`\) characters and prepends the translation source text.
@@ -90,7 +90,7 @@ The i18n metadata is surrounded by colon \(`:`\) characters and prepends the tra
<!--todo: replace with docs-code -->
<docs-code language="typescript">
$localize `:{i18n_metadata}:string_to_translate`
&dollar;localize `:{i18n_metadata}:string_to_translate`
</docs-code>
### Include interpolated text
@@ -100,13 +100,13 @@ Include [interpolations](guide/templates/interpolation) in a [`$localize`][ApiLo
<!--todo: replace with docs-code -->
<docs-code language="typescript">
$localize `string_to_translate ${variable_name}`;
&dollar;localize `string_to_translate &dollar;{variable_name}`;
</docs-code>
### Name the interpolation placeholder
<docs-code language="typescript">
$localize `string_to_translate ${variable_name}:placeholder_name:`;
&dollar;localize `string_to_translate &dollar;{variable_name}:placeholder_name:`;
</docs-code>
## i18n metadata for translation
@@ -114,7 +114,7 @@ $localize `string_to_translate ${variable_name}:placeholder_name:`;
<!--todo: replace with docs-code -->
<docs-code language="html">
{meaning}|{description}@@{custom_id}
{meaning}|{description}&commat;&commat;{custom_id}
</docs-code>
The following parameters provide context and additional information to reduce confusion for your translator.
@@ -143,7 +143,7 @@ The following example shows the value of the [`$localize`][ApiLocalizeInitLocali
<docs-code language="typescript">
$localize `:An introduction header for this sample:Hello i18n!`;
&dollar;localize `:An introduction header for this sample:Hello i18n!`;
</docs-code>
@@ -166,7 +166,7 @@ The following code example shows the value of the [`$localize`][ApiLocalizeInitL
<docs-code language="typescript">
$localize `:site header|An introduction header for this sample:Hello i18n!`;
&dollar;localize `:site header|An introduction header for this sample:Hello i18n!`;
</docs-code>
@@ -88,7 +88,7 @@ To create a translation file for a locale or language, complete the following ac
<docs-code language="file">
messages.xlf --> messages.{locale}.xlf
messages.xlf --&gt; messages.{locale}.xlf
</docs-code>
@@ -132,20 +132,20 @@ The following actions describe the translation process for French.
1. Open `messages.fr.xlf` and find the first `<trans-unit>` element.
This is a *translation unit*, also known as a *text node*, that represents the translation of the `<h1>` greeting tag that was previously marked with the `i18n` attribute.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-hello-before"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-hello-before"/>
The `id="introductionHeader"` is a [custom ID][GuideI18nOptionalManageMarkedText], but without the `@@` prefix required in the source HTML.
1. Duplicate the `<source>... </source>` element in the text node, rename it to `target`, and then replace the content with the French text.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>, after translation)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-hello"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;, after translation)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-hello"/>
In a more complex translation, the information and context in the [description and meaning elements][GuideI18nCommonPrepareAddHelpfulDescriptionsAndMeanings] help you choose the right words for translation.
1. Translate the other text nodes.
The following example displays the way to translate.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-other-nodes"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-other-nodes"/>
IMPORTANT: Don't change the IDs for translation units.
Each `id` attribute is generated by Angular and depends on the content of the component text and the assigned meaning.
@@ -169,7 +169,7 @@ To translate a `plural`, translate the ICU format match values.
The following example displays the way to translate.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-plural"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-plural"/>
## Translate alternate expressions
@@ -184,18 +184,18 @@ The following example displays a `select` ICU expression in the component templa
In this example, Angular extracts the expression into two translation units.
The first contains the text outside of the `select` clause, and uses a placeholder for `select` \(`<x id="ICU">`\):
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-select-1"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-select-1"/>
IMPORTANT: When you translate the text, move the placeholder if necessary, but don't remove it.
If you remove the placeholder, the ICU expression is removed from your translated application.
The following example displays the second translation unit that contains the `select` clause.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-select-2"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-select-2"/>
The following example displays both translation units after translation is complete.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-select"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translated-select"/>
## Translate nested expressions
@@ -206,15 +206,15 @@ Angular extracts the expression into two translation units.
The following example displays the first translation unit that contains the text outside of the nested expression.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-nested-1"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-nested-1"/>
The following example displays the second translation unit that contains the complete nested expression.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-nested-2"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-nested-2"/>
The following example displays both translation units after translating.
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-nested"/>
<docs-code header="src/locale/messages.fr.xlf (&lt;trans-unit&gt;)" path="adev/src/content/examples/i18n/doc-files/messages.fr.xlf.html" visibleRegion="translate-nested"/>
## What's next
+22 -22
View File
@@ -52,7 +52,7 @@ To activate the `NgOptimizedImage` directive, replace your image's `src` attribu
<docs-code language="typescript">
<img ngSrc="cat.jpg">
&lt;img ngSrc="cat.jpg"&gt;
</docs-code>
@@ -63,7 +63,7 @@ Always mark the [LCP image](https://web.dev/lcp/#what-elements-are-considered) o
<docs-code language="typescript">
<img ngSrc="cat.jpg" width="400" height="200" priority>
&lt;img ngSrc="cat.jpg" width="400" height="200" priority&gt;
</docs-code>
@@ -80,7 +80,7 @@ In order to prevent [image-related layout shifts](https://web.dev/css-web-vitals
<docs-code language="typescript">
<img ngSrc="cat.jpg" width="400" height="200">
&lt;img ngSrc="cat.jpg" width="400" height="200"&gt;
</docs-code>
@@ -100,7 +100,7 @@ When you add the `fill` attribute to your image, you do not need and should not
<docs-code language="typescript">
<img ngSrc="cat.jpg" fill>
&lt;img ngSrc="cat.jpg" fill&gt;
</docs-code>
@@ -118,17 +118,17 @@ IMPORTANT: For the "fill" image to render properly, its parent element **must**
NgOptimizedImage can display an automatic low-resolution placeholder for your image if you're using a CDN or image host that provides automatic image resizing. Take advantage of this feature by adding the `placeholder` attribute to your image:
<docs-code format="typescript" language="typescript">
<code-example format="typescript" language="typescript">
<img ngSrc="cat.jpg" width="400" height="200" placeholder>
&lt;img ngSrc="cat.jpg" width="400" height="200" placeholder&gt;
</docs-code>
</code-example>
Adding this attribute automatically requests a second, smaller version of the image using your specified image loader. This small image will be applied as a `background-image` style with a CSS blur while your image loads. If no image loader is provided, no placeholder image can be generated and an error will be thrown.
The default size for generated placeholders is 30px wide. You can change this size by specifying a pixel value in the `IMAGE_CONFIG` provider, as seen below:
<docs-code format="typescript" language="typescript">
<code-example format="typescript" language="typescript">
providers: [
{
provide: IMAGE_CONFIG,
@@ -137,7 +137,7 @@ providers: [
}
},
],
</docs-code>
</code-example>
If you want sharp edges around your blurred placeholder, you can wrap your image in a containing `<div>` with the `overflow: hidden` style. As long as the `<div>` is the same size as the image (such as by using the `width: fit-content` style), the "fuzzy edges" of the placeholder will be hidden.
@@ -145,11 +145,11 @@ If you want sharp edges around your blurred placeholder, you can wrap your image
You can also specify a placeholder using a base64 [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs) without an image loader. The data url format is `data:image/[imagetype];[data]`, where `[imagetype]` is the image format, just as `png`, and `[data]` is a base64 encoding of the image. That encoding can be done using the command line or in JavaScript. For specific commands, see [the MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs#encoding_data_into_base64_format). An example of a data URL placeholder with truncated data is shown below:
<docs-code format="typescript" language="typescript">
<code-example format="typescript" language="typescript">
<img ngSrc="cat.jpg" width="400" height="200" placeholder="data:image/png;base64,iVBORw0K...">
&lt;img ngSrc="cat.jpg" width="400" height="200" placeholder="data:image/png;base64,iVBORw0K..."&gt;
</docs-code>
</code-example>
However, large data URLs increase the size of your Angular bundles and slow down page load. If you cannot use an image loader, the Angular team recommends keeping base64 placeholder images smaller than 4KB and using them exclusively on critical images. In addition to decreasing placeholder dimensions, consider changing image formats or parameters used when saving images. At very low resolutions, these parameters can have a large effect on file size.
@@ -157,11 +157,11 @@ However, large data URLs increase the size of your Angular bundles and slow dow
By default, NgOptimizedImage applies a CSS blur effect to image placeholders. To render a placeholder without blur, provide a `placeholderConfig` argument with an object that includes the `blur` property, set to false. For example:
<docs-code format="typescript" language="typescript">
<code-example format="typescript" language="typescript">
<img ngSrc="cat.jpg" width="400" height="200" placeholder [placeholderConfig]="{blur: false}">
&lt;img ngSrc="cat.jpg" width="400" height="200" placeholder [placeholderConfig]="{blur: false}"&gt;
</docs-code>
</code-example>
## Adjusting image styling
@@ -181,7 +181,7 @@ You can add a [`preconnect` resource hint](https://web.dev/preconnect-and-dns-pr
<docs-code language="html">
<link rel="preconnect" href="https://my.cdn.origin" />
&lt;link rel="preconnect" href="https://my.cdn.origin" /&gt;
</docs-code>
@@ -236,7 +236,7 @@ If you would like to manually define a `srcset` attribute, you can provide your
<docs-code language="html">
<img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w">
&lt;img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w"&gt;
</docs-code>
@@ -244,7 +244,7 @@ If the `ngSrcset` attribute is present, `NgOptimizedImage` generates and sets th
<docs-code language="html">
<img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w" sizes="50vw">
&lt;img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w" sizes="50vw"&gt;
</docs-code>
@@ -254,7 +254,7 @@ To disable srcset generation for a single image, you can add the `disableOptimiz
<docs-code language="html">
<img ngSrc="about.jpg" disableOptimizedSrcset>
&lt;img ngSrc="about.jpg" disableOptimizedSrcset&gt;
</docs-code>
@@ -264,7 +264,7 @@ By default, `NgOptimizedImage` sets `loading=lazy` for all images that are not m
<docs-code language="html">
<img ngSrc="cat.jpg" width="400" height="200" loading="eager">
&lt;img ngSrc="cat.jpg" width="400" height="200" loading="eager"&gt;
</docs-code>
@@ -274,7 +274,7 @@ You may want to have images displayed at varying widths on differently-sized scr
<docs-code language="html">
<img ngSrc="cat.jpg" width="400" height="200" sizes="(max-width: 768px) 100vw, 50vw">
&lt;img ngSrc="cat.jpg" width="400" height="200" sizes="(max-width: 768px) 100vw, 50vw"&gt;
</docs-code>
@@ -371,7 +371,7 @@ Note that in the above example, we've invented the 'roundedCorners' property nam
<docs-code language="html">
<img ngSrc="profile.jpg" width="300" height="300" [loaderParams]="{roundedCorners: true}">
&lt;img ngSrc="profile.jpg" width="300" height="300" [loaderParams]="{roundedCorners: true}"&gt;
</docs-code>
+2 -2
View File
@@ -31,10 +31,10 @@ The following table summarizes the `@NgModule` metadata properties.
| Property | Details |
|:--- |:--- |
| `declarations` | A list of [declarable](/guide/ngmodules/faq#what-is-a-declarable?) classes (*components*, *directives*, and *pipes*) that *belong to this module*. <ol> <li> When compiling a template, you need to determine a set of selectors which should be used for triggering their corresponding directives. </li> <li> The template is compiled within the context of an NgModule —the NgModule within which the template's component is declared— which determines the set of selectors using the following rules: <ul> <li> All selectors of directives listed in `declarations`. </li> <li> All selectors of directives exported from imported NgModules. </li> </ul> </li> </ol> Components, directives, and pipes must belong to *exactly* one module. The compiler emits an error if you try to declare the same class in more than one module. Be careful not to re-declare a class that is imported directly or indirectly from another module. |
| `declarations` | A list of [declarable](/guide/ngmodules/faq#what-is-a-declarable?) classes (*components*, *directives*, and *pipes*) that *belong to this module*. <ol> <li> When compiling a template, you need to determine a set of selectors which should be used for triggering their corresponding directives. </li> <li> The template is compiled within the context of an NgModule &mdash;the NgModule within which the template's component is declared&mdash; which determines the set of selectors using the following rules: <ul> <li> All selectors of directives listed in `declarations`. </li> <li> All selectors of directives exported from imported NgModules. </li> </ul> </li> </ol> Components, directives, and pipes must belong to *exactly* one module. The compiler emits an error if you try to declare the same class in more than one module. Be careful not to re-declare a class that is imported directly or indirectly from another module. |
| `providers` | A list of dependency-injection providers. <br /> Angular registers these providers with the NgModule's injector. If it is the NgModule used for bootstrapping then it is the root injector. <br /> These services become available for injection into any component, directive, pipe or service which is a child of this injector. <br /> A lazy-loaded module has its own injector which is typically a child of the application root injector. <br /> Lazy-loaded services are scoped to the lazy module's injector. If a lazy-loaded module also provides the `UserService`, any component created within that module's context (such as by router navigation) gets the local instance of the service, not the instance in the root application injector. <br /> Components in external modules continue to receive the instance provided by their injectors. <br /> For more information on injector hierarchy and scoping, see [Providers](/guide/ngmodules/providers) and the [DI Guide](/guide/di). |
| `imports` | A list of modules which should be folded into this module. Folded means it is as if all the imported NgModule's exported properties were declared here. <br /> Specifically, it is as if the list of modules whose exported components, directives, or pipes are referenced by the component templates were declared in this module. <br /> A component template can [reference](/guide/ngmodules/faq#how-does-angular-find-components,-directives,-and-pipes-in-a-template?-what-is-a-template-reference?) another component, directive, or pipe when the reference is declared in this module or if the imported module has exported it. For example, a component can use the `NgIf` and `NgFor` directives only if the module has imported the Angular `CommonModule` (perhaps indirectly by importing `BrowserModule`). <br /> You can import many standard directives from the `CommonModule` but some familiar directives belong to other modules. For example, you can use `[(ngModel)]` only after importing the Angular `FormsModule`. |
| `exports` | A list of declarations —*component*, *directive*, and *pipe* classes— that an importing module can use. <br /> Exported declarations are the module's *public API*. A component in another module can use *this* module's `UserComponent` if it imports this module and this module exports `UserComponent`. <br /> Declarations are private by default. If this module does *not* export `UserComponent`, then only the components within *this* module can use `UserComponent`. <br /> Importing a module does *not* automatically re-export the imported module's imports. Module 'B' can't use `ngIf` just because it imported module 'A' which imported `CommonModule`. Module 'B' must import `CommonModule` itself. <br /> A module can list another module among its `exports`, in which case all of that module's public components, directives, and pipes are exported. <br /> [Re-export](/guide/ngmodules/faq#what-should-i-export?) makes module transitivity explicit. If Module 'A' re-exports `CommonModule` and Module 'B' imports Module 'A', Module 'B' components can use `ngIf` even though 'B' itself didn't import `CommonModule`. |
| `exports` | A list of declarations &mdash;*component*, *directive*, and *pipe* classes&mdash; that an importing module can use. <br /> Exported declarations are the module's *public API*. A component in another module can use *this* module's `UserComponent` if it imports this module and this module exports `UserComponent`. <br /> Declarations are private by default. If this module does *not* export `UserComponent`, then only the components within *this* module can use `UserComponent`. <br /> Importing a module does *not* automatically re-export the imported module's imports. Module 'B' can't use `ngIf` just because it imported module 'A' which imported `CommonModule`. Module 'B' must import `CommonModule` itself. <br /> A module can list another module among its `exports`, in which case all of that module's public components, directives, and pipes are exported. <br /> [Re-export](/guide/ngmodules/faq#what-should-i-export?) makes module transitivity explicit. If Module 'A' re-exports `CommonModule` and Module 'B' imports Module 'A', Module 'B' components can use `ngIf` even though 'B' itself didn't import `CommonModule`. |
| `bootstrap` | A list of components that are automatically bootstrapped. <br /> Usually there's only one component in this list, the *root component* of the application. <br /> Angular can launch with multiple bootstrap components, each with its own location in the host web page. |
## More on NgModules
+4 -4
View File
@@ -6,14 +6,14 @@ This page answers the questions many developers ask about NgModule design and im
## What classes should I add to the `declarations` array?
Add [declarable](/guide/ngmodules/bootstrapping#the-declarations-array) classes —components, directives, and pipes— to a `declarations` list.
Add [declarable](/guide/ngmodules/bootstrapping#the-declarations-array) classes &mdash;components, directives, and pipes&mdash; to a `declarations` list.
Declare these classes in *exactly one* module of the application.
Declare them in a module if they belong to that particular module.
## What is a `declarable`?
Declarables are the class types —components, directives, and pipes— that you can add to a module's `declarations` list.
Declarables are the class types &mdash;components, directives, and pipes&mdash; that you can add to a module's `declarations` list.
They're the only classes that you can add to `declarations`.
## What classes should I *not* add to `declarations`?
@@ -97,7 +97,7 @@ Export [declarable](/guide/ngmodules/bootstrapping#the-declarations-array) class
These are your *public* classes.
If you don't export a declarable class, it stays *private*, visible only to other components declared in this NgModule.
You *can* export any declarable class —components, directives, and pipes— whether
You *can* export any declarable class &mdash;components, directives, and pipes&mdash; whether
it's declared in this NgModule or in an imported NgModule.
You *can* re-export entire imported NgModules, which effectively re-export all of their exported classes.
@@ -389,7 +389,7 @@ In an Angular app, NgModules and JavaScript modules work together.
In modern JavaScript, every file is a module (see the [Modules](https://exploringjs.com/es6/ch_modules.html) page of the Exploring ES6 website).
Within each file you write an `export` statement to make parts of the module public.
An Angular NgModule is a class with the `@NgModule` decorator —JavaScript modules don't have to have the `@NgModule` decorator.
An Angular NgModule is a class with the `@NgModule` decorator &mdash;JavaScript modules don't have to have the `@NgModule` decorator.
Angular's `NgModule` has `imports` and `exports` and they serve a similar purpose.
You *import* other NgModules so you can use their exported classes in component templates.
@@ -1,7 +1,7 @@
# Lazy-loading feature modules
By default, NgModules are eagerly loaded. This means that as soon as the application loads, so do all the NgModules, whether they are immediately necessary or not.
For large applications with lots of routes, consider lazy loading —a design pattern that loads NgModules as needed.
For large applications with lots of routes, consider lazy loading &mdash;a design pattern that loads NgModules as needed.
Lazy loading helps keep initial bundle sizes smaller, which in turn helps decrease load times.
<!-- For the final sample application with two lazy-loaded modules that this page describes: -->
@@ -363,7 +363,7 @@ In the newly created service, implement the `Resolve` interface provided by the
import { Resolve } from '@angular/router';
…
&hellip;
/*An interface that represents your data model*/
export interface Crisis {
@@ -371,8 +371,8 @@ export interface Crisis {
name: string;
}
export class CrisisDetailResolverService implements Resolve<Crisis> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> {
export class CrisisDetailResolverService implements Resolve&lt;Crisis&gt; {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable&lt;Crisis&gt; {
// your logic goes here
}
}
@@ -405,7 +405,7 @@ In the component's constructor, inject an instance of the `ActivatedRoute` class
<docs-code header="Component's constructor (excerpt)">
import { ActivatedRoute } from '@angular/router';
@Component({ … })
@Component({ &hellip; })
class YourComponent {
constructor(private route: ActivatedRoute) {}
}
@@ -417,7 +417,7 @@ Use the injected instance of the `ActivatedRoute` class to access `data` associa
highlight="[1,5,8]">
import { ActivatedRoute } from '@angular/router';
@Component({ … })
@Component({ &hellip; })
class YourComponent {
constructor(private route: ActivatedRoute) {}
@@ -425,7 +425,7 @@ class YourComponent {
this.route.data
.subscribe(data => {
const crisis: Crisis = data.crisis;
// …
// &hellip;
});
}
}
@@ -1,7 +1,7 @@
# Guidelines for creating NgModules
This topic provides a conceptual overview of the different categories of NgModules you can create in order to organize your code in a modular structure.
These categories are not cast in stone —they are suggestions.
These categories are not cast in stone &mdash;they are suggestions.
You may want to create NgModules for other purposes, or combine the characteristics of some of these categories.
NgModules are a great way to organize an application and keep code related to a specific functionality or feature separate from other code.
@@ -14,7 +14,7 @@ For the module specification, see the [6th Edition of the ECMAScript standard](h
To make the code in a JavaScript module available to other modules, use an `export` statement at the end of the relevant code in the module, such as the following:
<docs-code language="typescript">
export class AppComponent { … }
export class AppComponent { &hellip; }
</docs-code>
When you need that module's code in another module, use an `import` statement as follows:
@@ -97,7 +97,7 @@ Make a pipe impure by setting its `pure` flag to `false`:
visibleRegion="pipe-decorator" highlight="[19]"/>
The following code shows the complete implementation of `FlyingHeroesImpurePipe`, which extends `FlyingHeroesPipe` to inherit its characteristics.
The example shows that you don't have to change anything else—the only difference is setting the `pure` flag as `false` in the pipe metadata.
The example shows that you don't have to change anything else&mdash;the only difference is setting the `pure` flag as `false` in the pipe metadata.
<docs-code-multifile>
<docs-code header="src/app/flying-heroes.pipe.ts (FlyingHeroesImpurePipe)" path="adev/src/content/examples/pipes/src/app/flying-heroes.pipe.ts" visibleRegion="impure"/>
@@ -380,7 +380,7 @@ The router uses an `id` to show the correct hero's details.
First, import the following members in the component you want to navigate from.
```ts
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute } from '&commat;angular/router';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
```
@@ -59,7 +59,7 @@ The data property is accessible within each activated route.
Use it to store items such as page titles, breadcrumb text, and other read-only, static data.
Use the resolve guard to retrieve dynamic data.
The empty path in the fourth route represents the default path for the application —the place to go when the path in the URL is empty, as it typically is at the start.
The empty path in the fourth route represents the default path for the application &mdash;the place to go when the path in the URL is empty, as it typically is at the start.
This default route redirects to the route for the `/heroes` URL and, therefore, displays the `HeroesListComponent`.
If you need to see what events are happening during the navigation lifecycle, there is the `withDebugTracing` feature.
@@ -74,8 +74,8 @@ It acts as a placeholder that marks the spot in the template where the router sh
<docs-code language="html">
<router-outlet></router-outlet>
<!-- Routed components go here -->
&lt;router-outlet&gt;&lt;/router-outlet&gt;
&lt;!-- Routed components go here --&gt;
</docs-code>
@@ -170,7 +170,7 @@ These events are shown in the following table.
| [`ChildActivationEnd`](api/router/ChildActivationEnd) | Triggered when the Router finishes activating a route's children. |
| [`ActivationEnd`](api/router/ActivationEnd) | Triggered when the Router finishes activating a route. |
| [`NavigationEnd`](api/router/NavigationEnd) | Triggered when navigation ends successfully. |
| [`NavigationCancel`](api/router/NavigationCancel) | Triggered when navigation is canceled. This can happen when a Route Guard returns false during navigation, or redirects by returning a `UrlTree`. |
| [`NavigationCancel`](api/router/NavigationCancel) | Triggered when navigation is canceled. This can happen when a Route Guard returns false during navigation, or redirects by returning a `UrlTree` or `RedirectCommand`. |
| [`NavigationError`](api/router/NavigationError) | Triggered when navigation fails due to an unexpected error. |
| [`Scroll`](api/router/Scroll) | Represents a scrolling event. |
+6 -54
View File
@@ -29,7 +29,7 @@ This is one of the most common attacks on the web.
To block XSS attacks, you must prevent malicious code from entering the Document Object Model (DOM).
For example, if attackers can trick you into inserting a `<script>` tag in the DOM, they can run arbitrary code on your website.
The attack isn't limited to `<script>` tags —many elements and properties in the DOM allow code execution, for example, `<img alt="" onerror="...">` and `<a href="javascript:...">`.
The attack isn't limited to `<script>` tags &mdash;many elements and properties in the DOM allow code execution, for example, `<img alt="" onerror="...">` and `<a href="javascript:...">`.
If attacker-controlled data enters the DOM, expect security vulnerabilities.
### Angular's cross-site scripting security model
@@ -72,7 +72,7 @@ The following template binds the value of `htmlSnippet`. Once by interpolating i
<docs-code header="src/app/inner-html-binding.component.html" path="adev/src/content/examples/security/src/app/inner-html-binding.component.html"/>
Interpolated content is always escaped —the HTML isn't interpreted and the browser displays angle brackets in the element's text content.
Interpolated content is always escaped &mdash;the HTML isn't interpreted and the browser displays angle brackets in the element's text content.
For the HTML to be interpreted, bind it to an HTML property such as `innerHTML`.
Be aware that binding a value that an attacker might control into `innerHTML` normally causes an XSS vulnerability.
@@ -93,7 +93,7 @@ Avoid directly interacting with the DOM and instead use Angular templates where
For cases where this is unavoidable, use the built-in Angular sanitization functions.
Sanitize untrusted values with the [DomSanitizer.sanitize](api/platform-browser/DomSanitizer#sanitize) method and the appropriate `SecurityContext`.
That function also accepts values that were marked as trusted using the `bypassSecurityTrust` … functions, and does not sanitize them, as [described below](#trusting-safe-values).
That function also accepts values that were marked as trusted using the `bypassSecurityTrust` &hellip; functions, and does not sanitize them, as [described below](#trusting-safe-values).
### Trusting safe values
@@ -155,7 +155,7 @@ You can set the nonce for Angular in one of two ways:
<docs-code language="typescript">
import {bootstrapApplication, CSP_NONCE} from '@angular/core';
import {bootstrapApplication, CSP_NONCE} from '&commat;angular/core';
import {AppComponent} from './app/app.component';
bootstrapApplication(AppComponent, {
@@ -301,56 +301,8 @@ Only code from the website on which cookies are set can read the cookies from th
That means only your application can read this cookie token and set the custom header.
The malicious code on `evil.com` can't.
### `HttpClient` XSRF/CSRF security
`HttpClient` supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token) used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`. Because only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
By default, an interceptor sends this header on all mutating requests (such as `POST`) to relative URLs, but not on GET/HEAD requests or on requests with an absolute URL.
<docs-callout helpful title="Why not protect GET requests?">
CSRF protection is only needed for requests that can change state on the backend. By their nature, CSRF attacks cross domain boundaries, and the web's [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) will prevent an attacking page from retrieving the results of authenticated GET requests.
</docs-callout>
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication cookie with a salt for added security.
To prevent collisions in environments where multiple Angular apps share the same domain or subdomain, give each application a unique cookie name.
<docs-callout important title="HttpClient supports only the client half of the XSRF protection scheme">
Your backend service must be configured to set the cookie for your page, and to verify that the header is present on all eligible requests. Failing to do so renders Angular's default protection ineffective.
</docs-callout>
### Configure custom cookie/header names
If your backend service uses different names for the XSRF token cookie or header, use `withXsrfConfiguration` to override the defaults.
Add it to the `provideHttpClient` call as follows:
<docs-code language="ts">
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withXsrfConfiguration({
cookieName: 'CUSTOM_XSRF_TOKEN',
headerName: 'X-Custom-Xsrf-Header',
}),
),
]
};
</docs-code>
### Disabling XSRF protection
If the built-in XSRF protection mechanism doesn't work for your application, you can disable it using the `withNoXsrfProtection` feature:
<docs-code language="ts">
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withNoXsrfProtection(),
),
]
};
</docs-code>
Angular's `HttpClient` has built-in support for the client-side half of this technique.
Read about it more in the [HttpClient guide](/guide/http/security#xsrf-csrf-protection).
For information about CSRF at the Open Web Application Security Project \(OWASP\), see [Cross-Site Request Forgery (CSRF)](https://owasp.org/www-community/attacks/csrf) and [Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html).
The Stanford University paper [Robust Defenses for Cross-Site Request Forgery](https://seclab.stanford.edu/websec/csrf/csrf.pdf) is a rich source of detail.
@@ -98,29 +98,3 @@ mySignal.set(3);
```
Here, only the last value (3) will be logged.
### `outputFromObservable`
`outputFromObservable(...)` declares an Angular output that emits values based on an RxJS observable.
```ts
class MyDir {
nameChange$ = new Observable<string>(/* ... */);
nameChange = outputFromObservable(this.nameChange$); // OutputRef<string>
}
```
See more details in the [output() API guide](/guide/output-function).
### `outputToObservable`
`outputToObservable(...)` converts an Angular output to an observable.
This allows you use integrate Angular outputs conveniently into RxJS streams.
```ts
outputToObservable(myComp.instance.onNameChange)
.pipe(...)
.subscribe(...)
```
See more details in the [output() API guide](/guide/output-function).
@@ -10,7 +10,7 @@ Then, you set the attribute value with an expression that resolves to a string.
<docs-code language="html">
<p [attr.attribute-you-are-targeting]="expression"></p>
&lt;p [attr.attribute-you-are-targeting]="expression"&gt;&lt;/p&gt;
</docs-code>
+2 -2
View File
@@ -30,7 +30,7 @@ Other notable differences from JavaScript syntax include:
## Expression context
Interpolated expressions have a context—a particular part of the application to which the expression belongs. Typically, this context is the component instance.
Interpolated expressions have a context&mdash;a particular part of the application to which the expression belongs. Typically, this context is the component instance.
In the following snippet, the expression `recommended` and the expression `itemImageUrl2` refer to properties of the `AppComponent`.
@@ -50,7 +50,7 @@ HELPFUL: Template expressions cannot refer to anything in the global namespace,
### Preventing name collisions
The context against which an expression evaluates is the union of the template variables, the directive's context object—if it has one—and the component's members.
The context against which an expression evaluates is the union of the template variables, the directive's context object&mdash;if it has one&mdash;and the component's members.
If you reference a name that belongs to more than one of these namespaces, Angular applies the following precedence logic to determine the context:
1. The template variable name.
@@ -8,7 +8,7 @@ To create a single class binding, type the following:
`[class.sale]="onSale"`
Angular adds the class when the bound expression, `onSale` is truthy, and it removes the class when the expression is falsy—with the exception of `undefined`. The behavior of `undefined` is specifically useful when multiple bindings to the same class exist across different directives on an element.
Angular adds the class when the bound expression, `onSale` is truthy, and it removes the class when the expression is falsy&mdash;with the exception of `undefined`.
## Binding to multiple CSS classes
@@ -24,7 +24,7 @@ The expression can be one of:
With the object format, Angular adds a class only if its associated value is truthy.
IMPORTANT: With any object-like expression—such as `object`, `Array`, `Map`, or `Set` —the identity of the object must change for Angular to update the class list.
IMPORTANT: With any object-like expression&mdash;such as `object`, `Array`, `Map`, or `Set` &mdash;the identity of the object must change for Angular to update the class list.
Updating the property without changing object identity has no effect.
If there are multiple bindings to the same class name, Angular uses styling precedence to determine which binding to use.
@@ -33,10 +33,10 @@ The following table summarizes class binding syntax.
| Binding Type | Syntax | Input Type | Example Input Values |
|:--- |:--- |:--- |:--- |
| Single class binding | `[class.sale]="onSale"` | <code>boolean | undefined | null</code> | `true`, `false` |
| Single class binding | `[class.sale]="onSale"` | <code>boolean &verbar; undefined &verbar; null</code> | `true`, `false` |
| Multi-class binding | `[class]="classExpression"` | `string` | `"my-class-1 my-class-2 my-class-3"` |
| Multi-class binding | `[class]="classExpression"` | <code>Record<string, boolean | undefined | null></code> | `{foo: true, bar: false}` |
| Multi-class binding | `[class]="classExpression"` | <code>Array<string></code> | `['foo', 'bar']` |
| Multi-class binding | `[class]="classExpression"` | <code>Record&lt;string, boolean &verbar; undefined &verbar; null&gt;</code> | `{foo: true, bar: false}` |
| Multi-class binding | `[class]="classExpression"` | <code>Array&lt;string&gt;</code> | `['foo', 'bar']` |
## Binding to a single style
@@ -48,15 +48,15 @@ Angular sets the property to the value of the bound expression, which is usually
1. To write a style in dash-case, type the following:
<docs-code language="html"><nav [style.background-color]="expression"></nav></docs-code>
<docs-code language="html">&lt;nav [style.background-color]="expression"&gt;&lt;/nav&gt;</docs-code>
2. To write a style in camelCase, type the following:
<docs-code language="html"><nav [style.backgroundColor]="expression"></nav></docs-code>
<docs-code language="html">&lt;nav [style.backgroundColor]="expression"&gt;&lt;/nav&gt;</docs-code>
## Binding to multiple styles
To toggle multiple styles, bind to the `[style]` attribute—for example, `[style]="styleExpression"`. The `styleExpression` can be one of:
To toggle multiple styles, bind to the `[style]` attribute&mdash;for example, `[style]="styleExpression"`. The `styleExpression` can be one of:
* A string list of styles such as `"width: 100px; height: 100px; background-color: cornflowerblue;"`.
* An object with style names as the keys and style values as the values, such as `{width: '100px', height: '100px', backgroundColor: 'cornflowerblue'}`.
@@ -76,10 +76,10 @@ The following table summarizes style binding syntax.
| Binding Type | Syntax | Input Type | Example Input Values |
|:--- |:--- |:--- |:--- |
| Single style binding | `[style.width]="width"` | <code>string | undefined | null</code> | `"100px"` |
| Single style binding with units | `[style.width.px]="width"` | <code>number | undefined | null</code> | `100` |
| Single style binding | `[style.width]="width"` | <code>string &verbar; undefined &verbar; null</code> | `"100px"` |
| Single style binding with units | `[style.width.px]="width"` | <code>number &verbar; undefined &verbar; null</code> | `100` |
| Multi-style binding | `[style]="styleExpression"` | `string` | `"width: 100px; height: 100px"` |
| Multi-style binding | `[style]="styleExpression"` | <code>Record<string, string | undefined | null></code> | `{width: '100px', height: '100px'}` |
| Multi-style binding | `[style]="styleExpression"` | <code>Record&lt;string, string &verbar; undefined &verbar; null&gt;</code> | `{width: '100px', height: '100px'}` |
## Styling precedence
@@ -12,7 +12,7 @@ This syntax consists of a target event name within parentheses to the left of an
Create the following example; the target event name is `click` and the template statement is `onSave()`.
<docs-code language="html" header="Event binding syntax">
<button (click)="onSave()">Save</button>
&lt;button (click)="onSave()"&gt;Save&lt;/button&gt;
</docs-code>
The event binding listens for the button's click events and calls the component's `onSave()` method whenever a click occurs.
@@ -31,7 +31,7 @@ If the target event name, `myClick` fails to match an output property of `ClickD
## Binding to keyboard events
You can bind to keyboard events using Angular's binding syntax. You can specify the key or code that you would like to bind to keyboard events. The `key` and `code` fields are a native part of the browser keyboard event object. By default, event binding assumes you want to use the `key` field on the keyboard event. You can also use the `code` field.
You can bind to keyboard events using Angular's binding syntax. You can specify the key or code that you would like to bind to keyboard events. They `key` and `code` fields are a native part of the browser keyboard event object. By default, event binding assumes you want to use the `key` field on the keyboard event. You can also use the `code` field.
Combinations of keys can be separated by a `.` (period). For example, `keydown.enter` will allow you to bind events to the `enter` key. You can also use modifier keys, such as `shift`, `alt`, `control`, and the `command` keys from Mac. The following example shows how to bind a keyboard event to `keydown.shift.t`.
@@ -30,7 +30,7 @@ The following JavaScript and template expression syntax is not allowed:
## Statement context
Statements have a context —a particular part of the application to which the statement belongs.
Statements have a context &mdash;a particular part of the application to which the statement belongs.
Statements can refer only to what's in the statement context, which is typically the component instance.
For example, `deleteHero()` of `(click)="deleteHero()"` is a method of the component in the following snippet.
@@ -33,7 +33,7 @@ To enable this, open the [Karma](https://karma-runner.github.io) test platform c
<docs-code language="javascript">
coverageReporter: {
dir: require('path').join(__dirname, './coverage/<project-name>'),
dir: require('path').join(__dirname, './coverage/&lt;project-name&gt;'),
subdir: '.',
reporters: [
{ type: 'html' },
@@ -126,7 +126,7 @@ Please call "TestBed.compileComponents" before your test.
You get this test failure message when the runtime environment compiles the source code *during the tests themselves*.
To correct the problem, call `compileComponents()` as explained in the following [Calling compileComponents](#calling-compilecomponents) section.
To correct the problem, call `compileComponents()` as explained in the following [Calling compileComponents](#compile-components) section.
## Component with a dependency
@@ -272,7 +272,7 @@ HELPFUL: The `it()` function receives an argument of the following form.
<docs-code language="javascript">
fakeAsync(() => { /*test body*/ })
fakeAsync(() =&gt; { /*test body*/ })
</docs-code>
@@ -593,7 +593,7 @@ The following test verifies that the hero name is propagated to the template usi
Because the [template](#dashboard-hero-component) passes the hero name through the Angular `UpperCasePipe`, the test must match the element value with the upper-cased name.
HELPFUL: This small test demonstrates how Angular tests can verify a component's visual representation —something not possible with [component class tests](guide/testing/components-basics#component-class-testing)— at low cost and without resorting to much slower and more complicated end-to-end tests.
HELPFUL: This small test demonstrates how Angular tests can verify a component's visual representation &mdash;something not possible with [component class tests](guide/testing/components-basics#component-class-testing)&mdash; at low cost and without resorting to much slower and more complicated end-to-end tests.
### Clicking
@@ -866,7 +866,7 @@ But there's plenty of template complexity even in this simple form.
<docs-code
path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.html" header="app/hero/hero-detail.component.html"/>
Tests that exercise the component need …
Tests that exercise the component need &hellip;
* To wait until a hero arrives before elements appear in the DOM
* A reference to the title text
@@ -1015,7 +1015,7 @@ One approach is to configure the testing module from the individual pieces as in
HELPFUL: Notice that the `beforeEach()` is asynchronous and calls `TestBed.compileComponents` because the `HeroDetailComponent` has an external template and css file.
As explained in [Calling `compileComponents()`](#calling-compilecomponents), these tests could be run in a non-CLI environment where Angular would have to compile them in the browser.
As explained in [Calling `compileComponents()`](#compile-components), these tests could be run in a non-CLI environment where Angular would have to compile them in the browser.
### Import a shared module
@@ -1089,10 +1089,10 @@ The [override metadata object](guide/testing/utility-apis#metadata-override-obje
<docs-code language="javascript">
type MetadataOverride<T> = {
add?: Partial<T>;
remove?: Partial<T>;
set?: Partial<T>;
type MetadataOverride&lt;T&gt; = {
add?: Partial&lt;T&gt;;
remove?: Partial&lt;T&gt;;
set?: Partial&lt;T&gt;;
};
</docs-code>
@@ -1108,7 +1108,7 @@ selector?: string;
template?: string;
templateUrl?: string;
providers?: any[];
…
&hellip;
</docs-code>
@@ -32,7 +32,7 @@ type TestModuleMetadata = {
providers?: any[];
declarations?: any[];
imports?: any[];
schemas?: Array<SchemaMetadata | any[]>;
schemas?: Array&lt;SchemaMetadata | any[]&gt;;
};
</docs-code>
@@ -41,10 +41,10 @@ Each override method takes a `MetadataOverride<T>` where `T` is the kind of meta
<docs-code language="javascript">
type MetadataOverride<T> = {
add?: Partial<T>;
remove?: Partial<T>;
set?: Partial<T>;
type MetadataOverride&lt;T&gt; = {
add?: Partial&lt;T&gt;;
remove?: Partial&lt;T&gt;;
set?: Partial&lt;T&gt;;
};
</docs-code>
@@ -60,7 +60,7 @@ Here are the most important static methods, in order of likely utility.
| Methods | Details |
|:--- |:--- |
| `configureTestingModule` | The testing shims \(`karma-test-shim`, `browser-test-shim`\) establish the [initial test environment](guide/testing) and a default testing module. The default testing module is configured with basic declaratives and some Angular service substitutes that every tester needs. <br /> Call `configureTestingModule` to refine the testing module configuration for a particular set of tests by adding and removing imports, declarations \(of components, directives, and pipes\), and providers. |
| `compileComponents` | Compile the testing module asynchronously after you've finished configuring it. You **must** call this method if *any* of the testing module components have a `templateUrl` or `styleUrls` because fetching component template and style files is necessarily asynchronous. See [compileComponents](guide/testing/components-scenarios#calling-compilecomponents). <br /> After calling `compileComponents`, the `TestBed` configuration is frozen for the duration of the current spec. |
| `compileComponents` | Compile the testing module asynchronously after you've finished configuring it. You **must** call this method if *any* of the testing module components have a `templateUrl` or `styleUrls` because fetching component template and style files is necessarily asynchronous. See [compileComponents](guide/testing/components-scenarios#compile-components). <br /> After calling `compileComponents`, the `TestBed` configuration is frozen for the duration of the current spec. |
| `createComponent<T>` | Create an instance of a component of type `T` based on the current `TestBed` configuration. After calling `createComponent`, the `TestBed` configuration is frozen for the duration of the current spec. |
| `overrideModule` | Replace metadata for the given `NgModule`. Recall that modules can import other modules. The `overrideModule` method can reach deeply into the current testing module to modify one of these inner modules. |
| `overrideComponent` | Replace metadata for the given component class, which could be nested deeply within an inner module. |
@@ -2,7 +2,7 @@
Handle user interaction in your application.
</docs-decorative-header>
The ability to handle user interaction and then work with - it is one of the key aspects of building dynamic applications. In this guide, we'll take a look at simple user interaction - event handling.
One of the key aspects of making applications dynamic is the ability to handle user interaction. In this guide, we'll take a look at two primary categories of user interaction: event handling and forms.
## Event Handling
@@ -36,10 +36,10 @@ The following properties are a set of options that customize the Angular CLI.
| Property | Details | Value type | Default value |
|:--- |:--- |:--- |:--- |
| `analytics` | Share anonymous usage data with the Angular Team. A boolean value indicates whether or not to share data, while a UUID string shares data using a pseudonymous identifier. | `boolean` | `string` | `false` |
| `analytics` | Share anonymous usage data with the Angular Team. A boolean value indicates whether or not to share data, while a UUID string shares data using a pseudonymous identifier. | `boolean` &verbar; `string` | `false` |
| `cache` | Control [persistent disk cache](cli/cache) used by [Angular CLI Builders](tools/cli/cli-builder). | [Cache options](#cache-options) | `{}` |
| `schematicCollections`| List schematics collections to use in `ng generate`. | `string[]` | `[]` |
| `packageManager` | The preferred package manager tool to use. | `npm` | `cnpm` | `pnpm` | `yarn` | `npm` |
| `packageManager` | The preferred package manager tool to use. | `npm` &verbar; `cnpm` &verbar; `pnpm` &verbar; `yarn` | `npm` |
| `warnings` | Control Angular CLI specific console warnings. | [Warnings options](#warnings-options) | `{}` |
### Cache options
@@ -47,7 +47,7 @@ The following properties are a set of options that customize the Angular CLI.
| Property | Details | Value type | Default value |
|:--- |:--- |:--- |:--- |
| `enabled` | Configure whether disk caching is enabled for builds. | `boolean` | `true` |
| `environment` | Configure in which environment disk cache is enabled.<br><br>* `ci` enables caching only in continuous integration (CI) environments.<br>* `local` enables caching only *outside* of CI environments.<br>* `all` enables caching everywhere. | `local` | `ci` | `all` | `local` |
| `environment` | Configure in which environment disk cache is enabled.<br><br>* `ci` enables caching only in continuous integration (CI) environments.<br>* `local` enables caching only *outside* of CI environments.<br>* `all` enables caching everywhere. | `local` &verbar; `ci` &verbar; `all` | `local` |
| `path` | The directory used to stored cache results. | `string` | `.angular/cache` |
### Warnings options
@@ -63,7 +63,7 @@ The following top-level configuration properties are available for each project,
| Property | Details | Value type | Default value |
|:--- |:--- |:--- |:--- |
| `root` | The root directory for this project's files, relative to the workspace directory. Empty for the initial application, which resides at the top level of the workspace. | `string` | None (required) |
| `projectType` | One of "application" or "library" An application can run independently in a browser, while a library cannot. | `application` | `library` | None (required) |
| `projectType` | One of "application" or "library" An application can run independently in a browser, while a library cannot. | `application` &verbar; `library` | None (required) |
| `sourceRoot` | The root directory for this project's source files. | `string` | `''` |
| `prefix` | A string that Angular prepends to selectors when generating new components, directives, and pipes using `ng generate`. Can be customized to identify an application or feature area. | `string` | `'app'` |
| `schematics` | A set of schematics that customize the `ng generate` sub-command option defaults for this project. See the [Generation schematics](#schematics) section. | See [schematics](#schematics) | `{}` |
@@ -409,8 +409,8 @@ Several options can be used to fine-tune the optimization of an application.
| Options | Details | Value type | Default value |
|:--- |:--- |:--- |:--- |
| `scripts` | Enables optimization of the scripts output. | `boolean` | `true` |
| `styles` | Enables optimization of the styles output. | `boolean` | [Styles optimization options](#styles-optimization-options) | `true` |
| `fonts` | Enables optimization for fonts. This requires internet access. | `boolean` | [Fonts optimization options](#fonts-optimization-options) | `true` |
| `styles` | Enables optimization of the styles output. | `boolean` &verbar; [Styles optimization options](#styles-optimization-options) | `true` |
| `fonts` | Enables optimization for fonts. This requires internet access. | `boolean` &verbar; [Fonts optimization options](#fonts-optimization-options) | `true` |
#### Styles optimization options
+1 -1
View File
@@ -12,7 +12,7 @@ Use the element name from the error message to search for places where you're us
@Component({
selector: 'YOUR_STRING',
…
&hellip;
})
</docs-code>
+2 -2
View File
@@ -17,10 +17,10 @@ Ensure that all dependencies are properly imported and declared in your NgModule
import { FormsModule } from '@angular/forms';
@NgModule({
…
&hellip;
imports: [
FormsModule,
…
&hellip;
</docs-code>
+2 -2
View File
@@ -11,7 +11,7 @@ Move all declarations:
<docs-code language="typescript">
const moduleDefinition = {…}
const moduleDefinition = {&hellip;}
@NgModule(moduleDefinition)
export class AppModule {
@@ -24,7 +24,7 @@ into the decorator:
<docs-code language="typescript">
@NgModule({…})
@NgModule({&hellip;})
export class AppModule {
constructor() {}
}
+2 -2
View File
@@ -19,7 +19,7 @@ Rename your component's selector so that it matches the requirements.
@Component({
selector: 'comp',
encapsulation: ViewEncapsulation.ShadowDom
…
&hellip;
})
</docs-code>
@@ -31,7 +31,7 @@ Rename your component's selector so that it matches the requirements.
@Component({
selector: 'app-comp',
encapsulation: ViewEncapsulation.ShadowDom
…
&hellip;
})
</docs-code>
+3 -3
View File
@@ -15,7 +15,7 @@ In order to make this reference, the compiler would have to add an import from `
<docs-code language="text">
parent.component.ts -> child.component.ts -> parent.component.ts
parent.component.ts -&gt; child.component.ts -&gt; parent.component.ts
</docs-code>
@@ -27,7 +27,7 @@ This is known as "remote scoping".
## Libraries
Unfortunately, "remote scoping" code is side-effectful —which prevents tree shaking— and cannot be used in libraries.
Unfortunately, "remote scoping" code is side-effectful &mdash;which prevents tree shaking&mdash; and cannot be used in libraries.
So when building libraries using the `"compilationMode": "partial"` setting, any component that would require a cyclic import will cause this `NG3003` compiler error to be raised.
## Debugging the error
@@ -38,7 +38,7 @@ For example:
<docs-code hideCopy="true">
The component ChildComponent is used in the template but importing it would create a cycle:
/parent.component.ts -> /child.component.ts -> /parent.component.ts
/parent.component.ts -&gt; /child.component.ts -&gt; /parent.component.ts
</docs-code>

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