mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
build: format md files
This commit configures prettier to format markdown files.
This commit is contained in:
parent
60ce31fb62
commit
3bed9f0f16
322 changed files with 15117 additions and 12358 deletions
|
|
@ -6,6 +6,7 @@ import {FormatConfig} from '@angular/ng-dev';
|
|||
export const format: FormatConfig = {
|
||||
'prettier': {
|
||||
'matchers': [
|
||||
'**/*.md',
|
||||
'**/*.{yaml,yml}',
|
||||
'**/*.{js,ts,mjs,mts,cjs,cts,tsx}',
|
||||
'devtools/**/*.{js,ts,mjs,mts,cjs,cts,html,scss}',
|
||||
|
|
@ -28,6 +29,9 @@ export const format: FormatConfig = {
|
|||
|
||||
// Ignore testing data files for language service
|
||||
'!vscode-ng-language-service/syntaxes/test/data/*.ts',
|
||||
|
||||
// Ignore goldens MD files
|
||||
'!/goldens/**/*.api.md',
|
||||
],
|
||||
},
|
||||
'buildifier': true,
|
||||
|
|
|
|||
8733
CHANGELOG.md
8733
CHANGELOG.md
File diff suppressed because it is too large
Load diff
10554
CHANGELOG_ARCHIVE.md
10554
CHANGELOG_ARCHIVE.md
File diff suppressed because it is too large
Load diff
|
|
@ -14,20 +14,20 @@ appearance, race, religion, or sexual identity and orientation.
|
|||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Use welcoming and inclusive language
|
||||
* Respect each other
|
||||
* Provide and gracefully accept constructive criticism
|
||||
* Show empathy towards other community members
|
||||
- Use welcoming and inclusive language
|
||||
- Respect each other
|
||||
- Provide and gracefully accept constructive criticism
|
||||
- Show empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* The use of sexualized language or imagery
|
||||
* Unwelcome sexual attention or advances
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- The use of sexualized language or imagery
|
||||
- Unwelcome sexual attention or advances
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
|
@ -69,9 +69,9 @@ members of the project's leadership.
|
|||
|
||||
If you are banned you may contest the decision. To do so email conduct@angular.io with the subject line "Repeal Ban for {{your name here}}" and body with the responses to the following:
|
||||
|
||||
* Why do you believe you did not violate the Code of Conduct?
|
||||
* Were other factors involved in this situation the leadership team may have been unaware of?
|
||||
* Why do you wish to be a part of the Angular community?
|
||||
- Why do you believe you did not violate the Code of Conduct?
|
||||
- Were other factors involved in this situation the leadership team may have been unaware of?
|
||||
- Why do you wish to be a part of the Angular community?
|
||||
|
||||
## Attribution
|
||||
|
||||
|
|
|
|||
136
CONTRIBUTING.md
136
CONTRIBUTING.md
|
|
@ -3,22 +3,20 @@
|
|||
We would love for you to contribute to Angular and help make it even better than it is today!
|
||||
As a contributor, here are the guidelines we would like you to follow:
|
||||
|
||||
- [Code of Conduct](#coc)
|
||||
- [Question or Problem?](#question)
|
||||
- [Issues and Bugs](#issue)
|
||||
- [Feature Requests](#feature)
|
||||
- [Submission Guidelines](#submit)
|
||||
- [Coding Rules](#rules)
|
||||
- [Commit Message Guidelines](#commit)
|
||||
- [Signing the CLA](#cla)
|
||||
|
||||
- [Code of Conduct](#coc)
|
||||
- [Question or Problem?](#question)
|
||||
- [Issues and Bugs](#issue)
|
||||
- [Feature Requests](#feature)
|
||||
- [Submission Guidelines](#submit)
|
||||
- [Coding Rules](#rules)
|
||||
- [Commit Message Guidelines](#commit)
|
||||
- [Signing the CLA](#cla)
|
||||
|
||||
## <a name="coc"></a> Code of Conduct
|
||||
|
||||
Help us keep Angular open and inclusive.
|
||||
Please read and follow our [Code of Conduct][coc].
|
||||
|
||||
|
||||
## <a name="question"></a> Got a Question or Problem?
|
||||
|
||||
Do not open issues for general support questions as we want to keep GitHub issues for bug reports and feature requests.
|
||||
|
|
@ -34,28 +32,25 @@ To save your and our time, we will systematically close all issues that are requ
|
|||
|
||||
If you would like to chat about the question in real-time, you can reach out via [the Angular community Discord server][discord].
|
||||
|
||||
|
||||
## <a name="issue"></a> Found a Bug?
|
||||
|
||||
If you find a bug in the source code, you can help us by [submitting an issue](#submit-issue) to our [GitHub Repository][github].
|
||||
Even better, you can [submit a Pull Request](#submit-pr) with a fix.
|
||||
|
||||
|
||||
## <a name="feature"></a> Missing a Feature?
|
||||
You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub Repository.
|
||||
If you would like to *implement* a new feature, please consider the size of the change in order to determine the right steps to proceed:
|
||||
|
||||
* For a **Major Feature**, first open an issue and outline your proposal so that it can be discussed.
|
||||
You can _request_ a new feature by [submitting an issue](#submit-issue) to our GitHub Repository.
|
||||
If you would like to _implement_ a new feature, please consider the size of the change in order to determine the right steps to proceed:
|
||||
|
||||
- For a **Major Feature**, first open an issue and outline your proposal so that it can be discussed.
|
||||
This process allows us to better coordinate our efforts, prevent duplication of work, and help you to craft the change so that it is successfully accepted into the project.
|
||||
|
||||
**Note**: Adding a new topic to the documentation, or significantly re-writing a topic, counts as a major feature.
|
||||
|
||||
* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
|
||||
|
||||
- **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr).
|
||||
|
||||
## <a name="submit"></a> Submission Guidelines
|
||||
|
||||
|
||||
### <a name="submit-issue"></a> Submitting an Issue
|
||||
|
||||
Before you submit an issue, please search the issue tracker. An issue for your problem might already exist and the discussion might inform you of workarounds readily available.
|
||||
|
|
@ -74,7 +69,6 @@ Unfortunately, we are not able to investigate / fix bugs without a minimal repro
|
|||
|
||||
You can file new issues by selecting from our [new issue templates](https://github.com/angular/angular/issues/new/choose) and filling out the issue template.
|
||||
|
||||
|
||||
### <a name="submit-pr"></a> Submitting a Pull Request (PR)
|
||||
|
||||
Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
|
|
@ -93,9 +87,9 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
|||
|
||||
5. In your forked repository, make your changes in a new git branch:
|
||||
|
||||
```shell
|
||||
git checkout -b my-fix-branch main
|
||||
```
|
||||
```shell
|
||||
git checkout -b my-fix-branch main
|
||||
```
|
||||
|
||||
6. Create your patch, **including appropriate test cases**.
|
||||
|
||||
|
|
@ -106,10 +100,11 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
|||
9. Commit your changes using a descriptive commit message that follows our [commit message conventions][commit-message-guidelines].
|
||||
Adherence to these conventions is necessary because release notes are automatically generated from these messages.
|
||||
|
||||
```shell
|
||||
git commit --all
|
||||
```
|
||||
Note: the optional commit `--all` command line option will automatically "add" and "rm" edited files.
|
||||
```shell
|
||||
git commit --all
|
||||
```
|
||||
|
||||
Note: the optional commit `--all` command line option will automatically "add" and "rm" edited files.
|
||||
|
||||
10. Push your branch to GitHub:
|
||||
|
||||
|
|
@ -133,16 +128,15 @@ If we ask for changes via code reviews then:
|
|||
|
||||
3. Create a fixup commit and push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```shell
|
||||
git commit --all --fixup HEAD
|
||||
git push
|
||||
```
|
||||
```shell
|
||||
git commit --all --fixup HEAD
|
||||
git push
|
||||
```
|
||||
|
||||
For more info on working with fixup commits see [here](./contributing-docs/using-fixup-commits.md).
|
||||
For more info on working with fixup commits see [here](./contributing-docs/using-fixup-commits.md).
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
|
||||
##### Updating the commit message
|
||||
|
||||
A reviewer might often suggest changes to a commit message (for example, to add more context for a change or adhere to our [commit message guidelines][commit-message-guidelines]).
|
||||
|
|
@ -150,69 +144,68 @@ In order to update the commit message of the last commit on your branch:
|
|||
|
||||
1. Check out your branch:
|
||||
|
||||
```shell
|
||||
git checkout my-fix-branch
|
||||
```
|
||||
```shell
|
||||
git checkout my-fix-branch
|
||||
```
|
||||
|
||||
2. Amend the last commit and modify the commit message:
|
||||
|
||||
```shell
|
||||
git commit --amend
|
||||
```
|
||||
```shell
|
||||
git commit --amend
|
||||
```
|
||||
|
||||
3. Push to your GitHub repository:
|
||||
|
||||
```shell
|
||||
git push --force-with-lease
|
||||
```
|
||||
```shell
|
||||
git push --force-with-lease
|
||||
```
|
||||
|
||||
> NOTE:<br />
|
||||
> If you need to update the commit message of an earlier commit, you can use `git rebase` in interactive mode.
|
||||
> See the [git docs](https://git-scm.com/docs/git-rebase#_interactive_mode) for more details.
|
||||
|
||||
|
||||
#### After your pull request is merged
|
||||
|
||||
After your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) repository:
|
||||
|
||||
* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
|
||||
- Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
|
||||
|
||||
```shell
|
||||
git push origin --delete my-fix-branch
|
||||
```
|
||||
```shell
|
||||
git push origin --delete my-fix-branch
|
||||
```
|
||||
|
||||
* Check out the main branch:
|
||||
- Check out the main branch:
|
||||
|
||||
```shell
|
||||
git checkout main -f
|
||||
```
|
||||
```shell
|
||||
git checkout main -f
|
||||
```
|
||||
|
||||
* Delete the local branch:
|
||||
- Delete the local branch:
|
||||
|
||||
```shell
|
||||
git branch -D my-fix-branch
|
||||
```
|
||||
```shell
|
||||
git branch -D my-fix-branch
|
||||
```
|
||||
|
||||
* Update your local `main` with the latest upstream version:
|
||||
|
||||
```shell
|
||||
git pull --ff upstream main
|
||||
```
|
||||
- Update your local `main` with the latest upstream version:
|
||||
|
||||
```shell
|
||||
git pull --ff upstream main
|
||||
```
|
||||
|
||||
## <a name="rules"></a> Coding Rules
|
||||
|
||||
To ensure consistency throughout the source code, keep these rules in mind as you are working:
|
||||
|
||||
* All features or bug fixes **must be tested** by one or more specs (unit-tests).
|
||||
* All public API methods **must be documented**.
|
||||
* We follow [Google's TypeScript Style Guide][ts-style-guide], but wrap all code at **100 characters**.
|
||||
|
||||
An automated formatter is available, see [building-and-testing-angular.md](./contributing-docs/building-and-testing-angular.md#formatting-your-source-code).
|
||||
- All features or bug fixes **must be tested** by one or more specs (unit-tests).
|
||||
- All public API methods **must be documented**.
|
||||
- We follow [Google's TypeScript Style Guide][ts-style-guide], but wrap all code at **100 characters**.
|
||||
|
||||
An automated formatter is available, see [building-and-testing-angular.md](./contributing-docs/building-and-testing-angular.md#formatting-your-source-code).
|
||||
|
||||
## <a name="commit"></a> Commit Message Guidelines
|
||||
|
||||
We have very precise rules over how our Git commit messages must be formatted:
|
||||
|
||||
```
|
||||
<type>(<scope>): <short summary>
|
||||
```
|
||||
|
|
@ -224,21 +217,18 @@ See [Commit Message Guidelines][commit-message-guidelines] for details.
|
|||
Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code
|
||||
changes to be accepted, the CLA must be signed. It's a quick process, we promise!
|
||||
|
||||
* For individuals, we have a [simple click-through form][individual-cla].
|
||||
* For corporations, we'll need you to
|
||||
- For individuals, we have a [simple click-through form][individual-cla].
|
||||
- For corporations, we'll need you to
|
||||
[print, sign and one of scan+email, fax or mail the form][corporate-cla].
|
||||
|
||||
If you have more than one GitHub accounts, or multiple email addresses associated with a single GitHub account, you must sign the CLA using the primary email address of the GitHub account used to author Git commits and send pull requests.
|
||||
|
||||
The following documents can help you sort out issues with GitHub accounts and multiple email addresses:
|
||||
|
||||
* https://help.github.com/articles/setting-your-commit-email-address-in-git/
|
||||
* https://stackoverflow.com/questions/37245303/what-does-usera-committed-with-userb-13-days-ago-on-github-mean
|
||||
* https://help.github.com/articles/about-commit-email-addresses/
|
||||
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
|
||||
|
||||
|
||||
|
||||
- https://help.github.com/articles/setting-your-commit-email-address-in-git/
|
||||
- https://stackoverflow.com/questions/37245303/what-does-usera-committed-with-userb-13-days-ago-on-github-mean
|
||||
- https://help.github.com/articles/about-commit-email-addresses/
|
||||
- https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
|
||||
|
||||
[coc]: https://github.com/angular/code-of-conduct/blob/main/CODE_OF_CONDUCT.md
|
||||
[corporate-cla]: https://cla.developers.google.com/about/google-corporate
|
||||
|
|
|
|||
|
|
@ -52,8 +52,7 @@ pnpm bazel clean
|
|||
pnpm bazel clean --expunge
|
||||
```
|
||||
|
||||
|
||||
## High level architecture
|
||||
## High level architecture
|
||||
|
||||
The angular.dev website is a modern Angular application that utilizes Static Site Generation (SSG) to deliver pre-rendered, high-performance content to users.
|
||||
|
||||
|
|
@ -61,4 +60,4 @@ The architecture is designed for efficiency and maintainability:
|
|||
|
||||
Content Sources: The documentation is sourced from two main places within the monorepo. The guides and tutorials are written in Markdown files, while the API reference pages are automatically generated by extracting documentation directly from the TypeScript source code of the Angular framework packages.
|
||||
|
||||
Build Process: During the build process, the Markdown files are converted into HTML. Simultaneously, the API documentation is extracted from the code comments. This content is then integrated into the Angular application.
|
||||
Build Process: During the build process, the Markdown files are converted into HTML. Simultaneously, the API documentation is extracted from the code comments. This content is then integrated into the Angular application.
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
`CommonModule`, `ApplicationRef.tick`, `Router.lastSuccessfulNavigation()`.
|
||||
`CommonModule`, `ApplicationRef.tick`, `Router.lastSuccessfulNavigation()`.
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ IMPORTANT: Use Important for information that's crucial to comprehending the tex
|
|||
|
||||
HELPFUL: Use Best practice to call out practices that are known to be successful or better than alternatives.
|
||||
|
||||
NOTE: THIS NOTE WITHOUT A LINE RETURN
|
||||
NOTE: THIS NOTE WITHOUT A LINE RETURN
|
||||
|
|
|
|||
|
|
@ -13,4 +13,4 @@
|
|||
|
||||
<docs-callout helpful title="Callout title for helpful">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
</docs-callout>
|
||||
</docs-callout>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<docs-card-container>
|
||||
<docs-card ></docs-card>
|
||||
<docs-card ></docs-card>
|
||||
</docs-card-container>
|
||||
</docs-card-container>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<docs-card title="No Link Card">Card Content</docs-card>
|
||||
<docs-card title="Link Card" link="Try It Now" href="in/app/link">
|
||||
Card Content with a symbol: `CommonModule`
|
||||
Card Content with a symbol: `CommonModule`
|
||||
</docs-card>
|
||||
<docs-card title="Image Card" imgSrc="./angular.svg"></docs-card>
|
||||
<docs-card title="" link="Open on Playground" href="/playground">
|
||||
The fastest way to play with an Angular app. No setup required.
|
||||
</docs-card>
|
||||
The fastest way to play with an Angular app. No setup required.
|
||||
</docs-card>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
```typescript
|
||||
this is a code block
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<docs-code-multifile>
|
||||
<docs-code >this is code</docs-code>
|
||||
<docs-code >this is also code</docs-code>
|
||||
</docs-code-multifile>
|
||||
</docs-code-multifile>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ this is code
|
|||
<docs-code path="./example-with-eslint-comment.ts" />
|
||||
<docs-code path="./example-with-region.ts" />
|
||||
|
||||
|
||||
<docs-code path="./new-code.ts"
|
||||
diff="./old-code.ts" />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<docs-decorative-header title="Custom Title" imgSrc="./decoration.svg">
|
||||
This is header text
|
||||
</docs-decorative-header>
|
||||
</docs-decorative-header>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<docs-pill-row>
|
||||
<docs-pill href="#pill-row" title="Link"/>
|
||||
<docs-pill href="#pill-row" title="Link"/>
|
||||
</docs-pill-row>
|
||||
</docs-pill-row>
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@ Text to force this as inline.
|
|||
<docs-pill href="./this-other-page" title="Another Page"/>
|
||||
<docs-pill href="./some-target-page" target="_some_target" title="Some Target Page"/>
|
||||
<docs-pill href="./download-page-example.ics" title="Download Page" download="file.ics"/>
|
||||
<docs-pill href="./download-page-example.ics" title="Download Page" download="file.ics" target="_some_target" />
|
||||
<docs-pill href="./download-page-example.ics" title="Download Page" download="file.ics" target="_some_target" />
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
</docs-step>
|
||||
<docs-step title="Step B">
|
||||
Do another thing after that.
|
||||
</docs-step>
|
||||
</docs-step>
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
This is a video:
|
||||
<docs-video src="https://www.youtube.com/embed/O47uUnJjbJc" alt="Expression Changed After Checked Video"/>
|
||||
<docs-video src="https://www.youtube.com/embed/O47uUnJjbJc" alt="Expression Changed After Checked Video"/>
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@
|
|||
<docs-step title="Step B">
|
||||
Do another thing after that.
|
||||
</docs-step>
|
||||
</docs-workflow>
|
||||
</docs-workflow>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,25 @@
|
|||
# Top Header (h1)
|
||||
|
||||
## Headers (h2)
|
||||
|
||||
### Smaller headers (h3)
|
||||
|
||||
#### Even smaller (h4)
|
||||
|
||||
##### Even more smaller (h5)
|
||||
|
||||
###### The smallest! (h6)
|
||||
|
||||
##### Another "more smaller" (h5)
|
||||
|
||||
## Duplicate Anchor
|
||||
|
||||
## Duplicate Anchor
|
||||
|
||||
## `myClass.myMethod` is the best
|
||||
|
||||
## ステップ 2 - アプリケーションのレイアウトに新しいコンポーネントを追加
|
||||
|
||||
## My heading {# my-custom-id }
|
||||
## Query for the `<h1>`
|
||||
|
||||
## Query for the `<h1>`
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||

|
||||

|
||||

|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[Angular Site](https://angular.dev)
|
||||
[same page](#test)
|
||||
[same site](../other/page)
|
||||
[npm packages](https://docs.npmjs.com/getting-started/what-is-npm "What is npm?")
|
||||
[npm packages](https://docs.npmjs.com/getting-started/what-is-npm 'What is npm?')
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
# Ordered List
|
||||
|
||||
1. First Item
|
||||
2. Another Item
|
||||
3. Last in order
|
||||
|
||||
# Unordered List
|
||||
|
||||
- Order
|
||||
- here
|
||||
- matter
|
||||
- doesn't
|
||||
- [some link](https://angular.dev)
|
||||
- Code block `SomeClass`
|
||||
- Code block `SomeClass`
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
| Sports | Season |
|
||||
| ---------------- | ------ |
|
||||
| Skiing | Winter |
|
||||
| Baseball | Summer |
|
||||
| Running | Year Round |
|
||||
| Sports | Season |
|
||||
| -------- | ---------- |
|
||||
| Skiing | Winter |
|
||||
| Baseball | Summer |
|
||||
| Running | Year Round |
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ This is a string that contains an emoji, 😎. See there it was;
|
|||
|
||||
# This header causes there to be two text areas
|
||||
|
||||
This is a string without an emoji.
|
||||
This is a string without an emoji.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
- [Deleting a file](#deleting-a-file)
|
||||
- [Switching a project](#switching-a-project)
|
||||
- [Components and services](#components-and-services)
|
||||
|
||||
- [EmbeddedEditor](#EmbeddedEditor)
|
||||
- [CodeEditor](#CodeEditor)
|
||||
- [CodeMirrorEditor](#CodeMirrorEditor)
|
||||
|
|
@ -43,13 +42,11 @@
|
|||
### Loading a project
|
||||
|
||||
1. The page responsible for the embedded editor lazy loads the [`EmbeddedEditor`](./embedded-editor.component.ts) component and the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts), then triggers the initialization of all components and services. The embedded editor is available in the following pages:
|
||||
|
||||
- homepage: https://angular.dev
|
||||
- playground: https://angular.dev/playground
|
||||
- tutorial pages: https://angular.dev/tutorials
|
||||
|
||||
2. The project assets are fetched by the [`EmbeddedTutorialManager`](./embedded-tutorial-manager.service.ts). Meanwhile:
|
||||
|
||||
- The code editor is initialized
|
||||
- The code editor initializes the TypeScript Web Worker, which initializes the "default file system map" using TypeScript's CDN.
|
||||
- The WebContainer is initialized
|
||||
|
|
@ -75,7 +72,6 @@
|
|||
2. The new file tab is opened.
|
||||
3. The user types the new file name.
|
||||
4. If the file name is valid, the file is created in the WebContainer filesystem by the [`NodeRuntimeSandbox`](./node-runtime-sandbox.service.ts).
|
||||
|
||||
- `..` is disallowed in the file name to prevent users to create files outside the `src` directory.
|
||||
|
||||
5. The file is added to the TypeScript virtual file system, allowing the TypeScript web worker to provide diagnostics, autocomplete and type features for the new file. Also, exports from the new file are available in other files.
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@ storyResource = resource({
|
|||
You can configure LLM APIs to return structured data. Strongly typing your `resource` to match the expected output from the LLM provides better type safety and editor autocompletion.
|
||||
|
||||
To manage state derived from a resource, use a `computed` signal or `linkedSignal`. Because `linkedSignal` [provides access to prior values](guide/signals/linked-signal), it can serve a variety of AI-related use cases, including
|
||||
* building a chat history
|
||||
* preserving or customizing data that templates display while LLMs generate content
|
||||
|
||||
- building a chat history
|
||||
- preserving or customizing data that templates display while LLMs generate content
|
||||
|
||||
In the example below, `storyParts` is a `linkedSignal` that appends the latest story parts returned from `storyResource` to the existing array of story parts.
|
||||
|
||||
|
|
@ -63,10 +64,10 @@ storyParts = linkedSignal<string[], string[]>({
|
|||
|
||||
LLM APIs may be slower and more error-prone than conventional, more deterministic APIs. You can use several Angular features to build a performant and user-friendly interface.
|
||||
|
||||
* **Scoped Loading:** place the `resource` in the component that directly uses the data. This helps limit change detection cycles (especially in zoneless applications) and prevents blocking other parts of your application. If data needs to be shared across multiple components, provide the `resource` from a service.
|
||||
* **SSR and Hydration:** use Server-Side Rendering (SSR) with incremental hydration to render the initial page content quickly. You can show a placeholder for the AI-generated content and defer fetching the data until the component hydrates on the client.
|
||||
* **Loading State:** use the `resource` `LOADING` [status](guide/signals/resource#resource-status) to show an indicator, like a spinner, while the request is in flight. This status covers both initial loads and reloads.
|
||||
* **Error Handling and Retries:** use the `resource` [**`reload()`**](guide/signals/resource#reloading) method as a simple way for users to retry failed requests, may be more prevalent when relying on AI generated content.
|
||||
- **Scoped Loading:** place the `resource` in the component that directly uses the data. This helps limit change detection cycles (especially in zoneless applications) and prevents blocking other parts of your application. If data needs to be shared across multiple components, provide the `resource` from a service.
|
||||
- **SSR and Hydration:** use Server-Side Rendering (SSR) with incremental hydration to render the initial page content quickly. You can show a placeholder for the AI-generated content and defer fetching the data until the component hydrates on the client.
|
||||
- **Loading State:** use the `resource` `LOADING` [status](guide/signals/resource#resource-status) to show an indicator, like a spinner, while the request is in flight. This status covers both initial loads and reloads.
|
||||
- **Error Handling and Retries:** use the `resource` [**`reload()`**](guide/signals/resource#reloading) method as a simple way for users to retry failed requests, may be more prevalent when relying on AI generated content.
|
||||
|
||||
The following example demonstrates how to create a responsive UI to dynamically display an AI generated image with loading and retry functionality.
|
||||
|
||||
|
|
@ -88,8 +89,8 @@ The following example demonstrates how to create a responsive UI to dynamically
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
## AI patterns in action: streaming chat responses
|
||||
|
||||
Interfaces often display partial results from LLM-based APIs incrementally as response data arrives. Angular's resource API provides the ability to stream responses to support this type of pattern. The `stream` property of `resource` accepts an asynchronous function you can use to apply updates to a signal value over time. The signal being updated represents the data being streamed.
|
||||
|
||||
```ts
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
# LLM prompts and AI IDE setup
|
||||
|
||||
Generating code with large language models (LLMs) is a rapidly growing area of interest for developers. While LLMs are often capable of generating working code it can be a challenge to generate code for consistently evolving frameworks like Angular.
|
||||
|
||||
Advanced instructions and prompting are an emerging standard for supporting modern code generation with domain specific details. This section contains curated content and resources to support more accurate code generation for Angular and LLMs.
|
||||
|
||||
## Custom Prompts and System Instructions
|
||||
|
||||
Improve your experience generating code with LLMs by using one of the following custom, domain specific files.
|
||||
|
||||
NOTE: These files will be updated on a regular basis staying up to date with Angular's conventions.
|
||||
|
|
@ -15,31 +17,33 @@ Here is a set of instructions to help LLMs generate correct code that follows An
|
|||
<a download href="/assets/context/best-practices.md" target="_blank">Click here to download the best-practices.md file.</a>
|
||||
|
||||
## Rules Files
|
||||
|
||||
Several editors, such as <a href="https://studio.firebase.google.com?utm_source=adev&utm_medium=website&utm_campaign=BUILD_WITH_AI_ANGULAR&utm_term=angular_devrel&utm_content=build_with_ai_angular_firebase_studio">Firebase Studio</a> have rules files useful for providing critical context to LLMs.
|
||||
|
||||
| Environment/IDE | Rules File | Installation Instructions |
|
||||
|:----------------|:----------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------|
|
||||
| Firebase Studio | <a download href="/assets/context/airules.md" target="_blank">airules.md</a> | <a href="https://firebase.google.com/docs/studio/set-up-gemini#custom-instructions">Configure `airules.md`</a> |
|
||||
| Copilot powered IDEs | <a download="copilot-instructions.md" href="/assets/context/guidelines.md" target="_blank">copilot-instructions.md</a> | <a href="https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions" target="_blank">Configure `.github/copilot-instructions.md`</a> |
|
||||
| Cursor | <a download href="/assets/context/angular-20.mdc" target="_blank">cursor.md</a> | <a href="https://docs.cursor.com/context/rules" target="_blank">Configure `cursorrules.md`</a> |
|
||||
| JetBrains IDEs | <a download href="/assets/context/guidelines.md" target="_blank">guidelines.md</a> | <a href="https://www.jetbrains.com/help/junie/customize-guidelines.html" target="_blank">Configure `guidelines.md`</a> |
|
||||
| VS Code | <a download=".instructions.md" href="/assets/context/guidelines.md" target="_blank">.instructions.md</a> | <a href="https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions" target="_blank">Configure `.instructions.md`</a> |
|
||||
| Windsurf | <a download href="/assets/context/guidelines.md" target="_blank">guidelines.md</a> | <a href="https://docs.windsurf.com/windsurf/cascade/memories#rules" target="_blank">Configure `guidelines.md`</a> |
|
||||
| Environment/IDE | Rules File | Installation Instructions |
|
||||
| :------------------- | :--------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Firebase Studio | <a download href="/assets/context/airules.md" target="_blank">airules.md</a> | <a href="https://firebase.google.com/docs/studio/set-up-gemini#custom-instructions">Configure `airules.md`</a> |
|
||||
| Copilot powered IDEs | <a download="copilot-instructions.md" href="/assets/context/guidelines.md" target="_blank">copilot-instructions.md</a> | <a href="https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions" target="_blank">Configure `.github/copilot-instructions.md`</a> |
|
||||
| Cursor | <a download href="/assets/context/angular-20.mdc" target="_blank">cursor.md</a> | <a href="https://docs.cursor.com/context/rules" target="_blank">Configure `cursorrules.md`</a> |
|
||||
| JetBrains IDEs | <a download href="/assets/context/guidelines.md" target="_blank">guidelines.md</a> | <a href="https://www.jetbrains.com/help/junie/customize-guidelines.html" target="_blank">Configure `guidelines.md`</a> |
|
||||
| VS Code | <a download=".instructions.md" href="/assets/context/guidelines.md" target="_blank">.instructions.md</a> | <a href="https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions" target="_blank">Configure `.instructions.md`</a> |
|
||||
| Windsurf | <a download href="/assets/context/guidelines.md" target="_blank">guidelines.md</a> | <a href="https://docs.windsurf.com/windsurf/cascade/memories#rules" target="_blank">Configure `guidelines.md`</a> |
|
||||
|
||||
## Angular CLI MCP Server setup
|
||||
|
||||
The Angular CLI includes an experimental [Model Context Protocol (MCP) server](https://modelcontextprotocol.io/) that allows AI assistants in your development environment to interact with the Angular CLI.
|
||||
|
||||
[**Learn how to set up the Angular CLI MCP Server**](/ai/mcp)
|
||||
|
||||
## Providing Context with `llms.txt`
|
||||
|
||||
`llms.txt` is a proposed standard for websites designed to help LLMs better understand and process their content. The Angular team has developed two versions of this file to help LLMs and tools that use LLMs for code generation to create better modern Angular code.
|
||||
|
||||
|
||||
* <a href="/llms.txt" target="_blank">llms.txt</a> - an index file providing links to key files and resources.
|
||||
* <a href="/assets/context/llms-full.txt" target="_blank">llms-full.txt</a> - a more robust compiled set of resources describing how Angular works and how to build Angular applications.
|
||||
- <a href="/llms.txt" target="_blank">llms.txt</a> - an index file providing links to key files and resources.
|
||||
- <a href="/assets/context/llms-full.txt" target="_blank">llms-full.txt</a> - a more robust compiled set of resources describing how Angular works and how to build Angular applications.
|
||||
|
||||
Be sure [to check out the overview page](/ai) for more information on how to integrate AI into your Angular applications.
|
||||
|
||||
## Web Codegen Scorer
|
||||
The Angular team developed and open-sourced the [Web Codegen Scorer](https://github.com/angular/web-codegen-scorer), a tool to evaluate and score the quality of AI generated web code. You can use this tool to make evidence-based decisions relating to AI-generated code, such as fine-tuning prompts to improve the accuracy of LLM-generated code for Angular. These prompts can be included as system instructions for your AI tooling or as context with your prompt. You can also use this tool to compare the quality of code produced by different models and monitor quality over time as models and agents evolve.
|
||||
|
||||
The Angular team developed and open-sourced the [Web Codegen Scorer](https://github.com/angular/web-codegen-scorer), a tool to evaluate and score the quality of AI generated web code. You can use this tool to make evidence-based decisions relating to AI-generated code, such as fine-tuning prompts to improve the accuracy of LLM-generated code for Angular. These prompts can be included as system instructions for your AI tooling or as context with your prompt. You can also use this tool to compare the quality of code produced by different models and monitor quality over time as models and agents evolve.
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ The Angular CLI MCP server provides several tools to assist you in your developm
|
|||
|
||||
| Name | Description | `local-only` | `read-only` |
|
||||
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------: | :---------: |
|
||||
| `ai_tutor` | Launches an interactive AI-powered Angular tutor. Recommended to run from a new Angular project using v20 or later. [Learn more](https://github.com/angular/ai-tutor/blob/main/README.md). | ✅ | ✅ |
|
||||
| `get_best_practices` | Retrieves the Angular Best Practices Guide. This guide is essential for ensuring that all code adheres to modern standards, including standalone components, typed forms, and modern control flow. | ✅ | ✅ |
|
||||
| `list_projects` | Lists the names of all applications and libraries defined within an Angular workspace. It reads the `angular.json` configuration file to identify the projects. | ✅ | ✅ |
|
||||
| `search_documentation` | Searches the official Angular documentation at https://angular.dev. This tool should be used to answer any questions about Angular, such as for APIs, tutorials, and best practices. | ❌ | ✅ |
|
||||
| `ai_tutor` | Launches an interactive AI-powered Angular tutor. Recommended to run from a new Angular project using v20 or later. [Learn more](https://github.com/angular/ai-tutor/blob/main/README.md). | ✅ | ✅ |
|
||||
| `get_best_practices` | Retrieves the Angular Best Practices Guide. This guide is essential for ensuring that all code adheres to modern standards, including standalone components, typed forms, and modern control flow. | ✅ | ✅ |
|
||||
| `list_projects` | Lists the names of all applications and libraries defined within an Angular workspace. It reads the `angular.json` configuration file to identify the projects. | ✅ | ✅ |
|
||||
| `search_documentation` | Searches the official Angular documentation at https://angular.dev. This tool should be used to answer any questions about Angular, such as for APIs, tutorials, and best practices. | ❌ | ✅ |
|
||||
|
||||
## Get Started
|
||||
|
||||
|
|
@ -118,12 +118,11 @@ For other IDEs, check your IDE's documentation for the proper location of the MC
|
|||
|
||||
The `mcp` command can be configured with the following options passed as arguments in your IDE's MCP configuration:
|
||||
|
||||
| Option | Type | Description | Default |
|
||||
| :------------- | :-------- | :--------------------------------------------------------------------------------------------------------- | :------ |
|
||||
| `--read-only` | `boolean` | Only register tools that do not make changes to the project. Your editor or coding agent may still perform edits. | `false` |
|
||||
| Option | Type | Description | Default |
|
||||
| :------------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------- | :------ |
|
||||
| `--read-only` | `boolean` | Only register tools that do not make changes to the project. Your editor or coding agent may still perform edits. | `false` |
|
||||
| `--local-only` | `boolean` | Only register tools that do not require an internet connection. Your editor or coding agent may still send data over the network. | `false` |
|
||||
|
||||
|
||||
For example, to run the server in read-only mode in VS Code, you would update your `mcp.json` like this:
|
||||
|
||||
```json
|
||||
|
|
@ -139,4 +138,4 @@ For example, to run the server in read-only mode in VS Code, you would update yo
|
|||
|
||||
## Feedback and New Ideas
|
||||
|
||||
The Angular team welcomes your feedback on the existing MCP capabilities and any ideas you have for new tools or features. Please share your thoughts by opening an issue on the [angular/angular GitHub repository](https://github.com/angular/angular/issues).
|
||||
The Angular team welcomes your feedback on the existing MCP capabilities and any ideas you have for new tools or features. Please share your thoughts by opening an issue on the [angular/angular GitHub repository](https://github.com/angular/angular/issues).
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<!-- TODO: need an Angular + AI logo -->
|
||||
|
||||
<docs-decorative-header title="Build with AI" imgSrc="adev/src/assets/images/what_is_angular.svg"> <!-- markdownlint-disable-line -->
|
||||
Build AI-powered apps. Develop faster with AI.
|
||||
</docs-decorative-header>
|
||||
|
|
@ -9,9 +10,9 @@ Generative AI (GenAI) with large language models (LLMs) enables the creation of
|
|||
|
||||
Developing features like these would have previously required deep domain expertise and significant engineering effort. However, new products and SDKs are lowering the barrier to entry. Angular is well-suited for integrating AI into your web application as a result of:
|
||||
|
||||
* Angular's robust templating APIs enable the creation of dynamic, cleanly composed UIs made from generated content
|
||||
* Strong, signal-based architecture designed to dynamically manage data and state
|
||||
* Angular integrates seamlessly with AI SDKs and APIs
|
||||
- Angular's robust templating APIs enable the creation of dynamic, cleanly composed UIs made from generated content
|
||||
- Strong, signal-based architecture designed to dynamically manage data and state
|
||||
- Angular integrates seamlessly with AI SDKs and APIs
|
||||
|
||||
This guide demonstrates how you can use [Genkit](/ai#build-ai-powered-applications-with-genkit-and-angular), [Firebase AI Logic](/ai#build-ai-powered-applications-with-firebase-ai-logic-and-angular), and the [Gemini API](/ai#build-ai-powered-applications-with-gemini-api-and-angular) to infuse your Angular apps with AI today. This guide will jumpstart your AI-powered web app development journey by explaining how to begin integrating AI into Angular apps. This guide also shares resources, such as starter kits, example code, and recipes for common workflows, you can use to get up to speed quickly.
|
||||
|
||||
|
|
@ -20,77 +21,88 @@ To get started, you should have a basic understanding of Angular. New to Angular
|
|||
NOTE: While this page features integrations and examples with Google AI products, tools like Genkit are model agnostic and allow you to choose your own model. In many cases, the examples and code samples are applicable to other third-party solutions.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Building AI-powered applications is a new and rapidly developing field. It can be challenging to decide where to start and which technologies to choose. The following section provides three options to choose from:
|
||||
|
||||
1. *Genkit* gives you the choice of [supported model and interface with a unified API](https://genkit.dev) for building full-stack applications. Ideal for applications requiring sophisticated back-end AI logic, such as personalized recommendations.
|
||||
1. _Genkit_ gives you the choice of [supported model and interface with a unified API](https://genkit.dev) for building full-stack applications. Ideal for applications requiring sophisticated back-end AI logic, such as personalized recommendations.
|
||||
|
||||
1. *Firebase AI Logic* provides a secure client-side API for Google's models to build client-side only applications or mobile apps. Best for interactive AI features directly in the browser, such as real-time text analysis or basic chatbots.
|
||||
1. _Firebase AI Logic_ provides a secure client-side API for Google's models to build client-side only applications or mobile apps. Best for interactive AI features directly in the browser, such as real-time text analysis or basic chatbots.
|
||||
|
||||
1. *Gemini API* enables you to build an application that uses the methods and functionality exposed through the API surface directly, best for full-stack applications. Suitable for applications needing direct control over AI models, like custom image generation or deep data processing.
|
||||
1. _Gemini API_ enables you to build an application that uses the methods and functionality exposed through the API surface directly, best for full-stack applications. Suitable for applications needing direct control over AI models, like custom image generation or deep data processing.
|
||||
|
||||
### Build AI-powered applications with Genkit and Angular
|
||||
[Genkit](https://genkit.dev) is an open-source toolkit designed to help you build AI-powered features in web and mobile apps. It offers a unified interface for integrating AI models from Google, OpenAI, Anthropic, Ollama, and more, so you can explore and choose the best models for your needs. As a server-side solution, your web apps need a supported server environment, such as a node-based server in order to integrate with Genkit. Building a full-stack app using Angular SSR gives you the starting server-side code, for example.
|
||||
|
||||
[Genkit](https://genkit.dev) is an open-source toolkit designed to help you build AI-powered features in web and mobile apps. It offers a unified interface for integrating AI models from Google, OpenAI, Anthropic, Ollama, and more, so you can explore and choose the best models for your needs. As a server-side solution, your web apps need a supported server environment, such as a node-based server in order to integrate with Genkit. Building a full-stack app using Angular SSR gives you the starting server-side code, for example.
|
||||
|
||||
Here are examples of how to build with Genkit and Angular:
|
||||
|
||||
* [Agentic Apps with Genkit and Angular starter-kit](https://github.com/angular/examples/tree/main/genkit-angular-starter-kit)— New to building with AI? Start here with a basic app that features an agentic workflow. Perfect place to start for your first AI building experience.
|
||||
- [Agentic Apps with Genkit and Angular starter-kit](https://github.com/angular/examples/tree/main/genkit-angular-starter-kit)— New to building with AI? Start here with a basic app that features an agentic workflow. Perfect place to start for your first AI building experience.
|
||||
|
||||
* [Use Genkit in an Angular app](https://genkit.dev/docs/angular/)— Build a basic application that uses Genkit Flows, Angular and Gemini 2.5 Flash. This step-by-step walkthrough guides you through creating a full-stack Angular application with AI features.
|
||||
- [Use Genkit in an Angular app](https://genkit.dev/docs/angular/)— Build a basic application that uses Genkit Flows, Angular and Gemini 2.5 Flash. This step-by-step walkthrough guides you through creating a full-stack Angular application with AI features.
|
||||
|
||||
* [Dynamic Story Generator app](https://github.com/angular/examples/tree/main/genkit-angular-story-generator)— Learn to build an agentic Angular app powered by Genkit, Gemini and Imagen 3 to dynamically generate a story based on user interaction featuring beautiful image panels to accompany the events that take place. Start here if you'd like to experiment with a more advanced use-case.
|
||||
- [Dynamic Story Generator app](https://github.com/angular/examples/tree/main/genkit-angular-story-generator)— Learn to build an agentic Angular app powered by Genkit, Gemini and Imagen 3 to dynamically generate a story based on user interaction featuring beautiful image panels to accompany the events that take place. Start here if you'd like to experiment with a more advanced use-case.
|
||||
|
||||
This example also has an in-depth video walkthrough of the functionality:
|
||||
* [Watch "Building Agentic Apps with Angular and Genkit live!"](https://youtube.com/live/mx7yZoIa2n4?feature=share)
|
||||
* [Watch "Building Agentic Apps with Angular and Genkit live! PT 2"](https://youtube.com/live/YR6LN5_o3B0?feature=share)
|
||||
- [Watch "Building Agentic Apps with Angular and Genkit live!"](https://youtube.com/live/mx7yZoIa2n4?feature=share)
|
||||
- [Watch "Building Agentic Apps with Angular and Genkit live! PT 2"](https://youtube.com/live/YR6LN5_o3B0?feature=share)
|
||||
|
||||
* [Building Agentic apps with Firebase and Google Cloud (Barista Example)](https://developers.google.com/solutions/learn/agentic-barista) - Learn how to build an agentic coffee ordering app with Firebase and Google Cloud. This example uses both Firebase AI Logic and Genkit.
|
||||
- [Building Agentic apps with Firebase and Google Cloud (Barista Example)](https://developers.google.com/solutions/learn/agentic-barista) - Learn how to build an agentic coffee ordering app with Firebase and Google Cloud. This example uses both Firebase AI Logic and Genkit.
|
||||
|
||||
### Build AI-powered applications with Firebase AI Logic and Angular
|
||||
|
||||
[Firebase AI Logic](https://firebase.google.com/products/vertex-ai-in-firebase) provides a secure way to interact with Vertex AI Gemini API or Imagen API directly from your web and mobile apps. This is compelling for Angular developers since apps can be either full-stack or client-side only. If you are developing a client-side only application, Firebase AI Logic is a good fit for incorporating AI into your web apps.
|
||||
|
||||
Here is an example of how to build with Firebase AI Logic and Angular:
|
||||
* [Firebase AI Logic x Angular Starter Kit](https://github.com/angular/examples/tree/main/vertex-ai-firebase-angular-example) - Use this starter-kit to build an e-commerce application with a chat agent that can perform tasks. Start here if you do not have experience building with Firebase AI Logic and Angular.
|
||||
|
||||
- [Firebase AI Logic x Angular Starter Kit](https://github.com/angular/examples/tree/main/vertex-ai-firebase-angular-example) - Use this starter-kit to build an e-commerce application with a chat agent that can perform tasks. Start here if you do not have experience building with Firebase AI Logic and Angular.
|
||||
|
||||
This example includes an [in-depth video walkthrough explaining the functionality and demonstrates how to add new features](https://youtube.com/live/4vfDz2al_BI).
|
||||
|
||||
### Build AI-powered applications with Gemini API and Angular
|
||||
|
||||
The [Gemini API](https://ai.google.dev/gemini-api/docs) provides access to state-of-the-art models from Google that support audio, images, video, and text input. These models that are optimized for specific use cases, [learn more on the Gemini API documentation site](https://ai.google.dev/gemini-api/docs/models).
|
||||
|
||||
* [AI Text Editor Angular app template](https://github.com/FirebaseExtended/firebase-framework-tools/tree/main/starters/angular/ai-text-editor) - Use this template to start with a fully functioning text editor with AI-powered features like refining text, expanding text and formalizing text. This is a good starting point to gain experience with calling the Gemini API via HTTP.
|
||||
- [AI Text Editor Angular app template](https://github.com/FirebaseExtended/firebase-framework-tools/tree/main/starters/angular/ai-text-editor) - Use this template to start with a fully functioning text editor with AI-powered features like refining text, expanding text and formalizing text. This is a good starting point to gain experience with calling the Gemini API via HTTP.
|
||||
|
||||
* [AI Chatbot app template](https://github.com/FirebaseExtended/firebase-framework-tools/tree/main/starters/angular/ai-chatbot) - This template starts with a chatbot user interface that communicates with the Gemini API via HTTP.
|
||||
- [AI Chatbot app template](https://github.com/FirebaseExtended/firebase-framework-tools/tree/main/starters/angular/ai-chatbot) - This template starts with a chatbot user interface that communicates with the Gemini API via HTTP.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Connecting to model providers and keeping your API Credentials Secure
|
||||
When connecting to model providers, it is important to keep your API secrets safe. *Never put your API key in a file that ships to the client, such as `environments.ts`*.
|
||||
|
||||
When connecting to model providers, it is important to keep your API secrets safe. _Never put your API key in a file that ships to the client, such as `environments.ts`_.
|
||||
|
||||
Your application's architecture determines which AI APIs and tools to choose. Specifically, choose based on whether or not your application is client-side or server-side. Tools such as Firebase AI Logic provide a secure connection to the model APIs for client-side code. If you want to use a different API than Firebase AI Logic or prefer to use a different model provider, consider creating a proxy-server or even [Cloud Functions for Firebase](https://firebase.google.com/docs/functions) to serve as a proxy and not expose your API keys.
|
||||
|
||||
For an example of connecting using a client-side app, see the code: [Firebase AI Logic Angular example repository](https://github.com/angular/examples/tree/main/vertex-ai-firebase-angular-example).
|
||||
For an example of connecting using a client-side app, see the code: [Firebase AI Logic Angular example repository](https://github.com/angular/examples/tree/main/vertex-ai-firebase-angular-example).
|
||||
|
||||
For server-side connections to model APIs that require API keys, prefer using a secrets manager or environment variable, not `environments.ts`. You should follow standard best practices for securing API keys and credentials. Firebase now provides a new secrets manager with the latest updates from Firebase App Hosting. To learn more, [check out the official documentation](https://firebase.google.com/docs/app-hosting/configure).
|
||||
|
||||
For a server-side connection example in a full-stack application, see the code: [Angular AI Example (Genkit and Angular Story Generator) repository](https://github.com/angular/examples/tree/main/genkit-angular-story-generator).
|
||||
|
||||
### Use Tool Calling to enhance apps
|
||||
|
||||
If you want to build agentic workflows, where agents are able to act and use tools to solve problems based on prompts use "tool calling". Tool calling, also known as function calling, is a way to provide LLMs the ability to make requests back to the application that called it. As a developer, you define which tools are available and you are in control of how or when the tools are called.
|
||||
|
||||
Tool calling further enhances your web apps by expanding your AI integration further than a question and answer style chat bot. In fact, you can empower your model to request function calls using the function calling API of your model provider. The available tools can be used to perform more complex actions within the context of your application.
|
||||
Tool calling further enhances your web apps by expanding your AI integration further than a question and answer style chat bot. In fact, you can empower your model to request function calls using the function calling API of your model provider. The available tools can be used to perform more complex actions within the context of your application.
|
||||
|
||||
In the [e-commerce example](https://github.com/angular/examples/blob/main/vertex-ai-firebase-angular-example/src/app/ai.service.ts#L88) of the [Angular examples repository](https://github.com/angular/examples), the LLM requests to make calls to functions for inventory in order to gain the necessary context to perform more complex tasks such as calculating how much a group of items in the store will cost. The scope of the available API is up to you as a developer just as is whether or not to call a function requested by the LLM. You remain in control of the flow of execution. You can expose specific functions of a service for example but not all functions of that service.
|
||||
|
||||
### Handling non-deterministic responses
|
||||
|
||||
Because models can return non-deterministic results, your applications should be designed with that in mind. Here are a few strategies that you can use in your application implementation:
|
||||
* Adjust prompts and model parameters (such as [temperature](https://ai.google.dev/gemini-api/docs/prompting-strategies)) for more or less deterministic responses. You can [find out more in the prompting strategies section](https://ai.google.dev/gemini-api/docs/prompting-strategies) of [ai.google.dev](https://ai.google.dev/).
|
||||
* Use the "human in the loop" strategy where a human verifies outputs before proceeding in a workflow. Build your application workflows to allow operators (humans or other models) to verify outputs and confirm key decisions.
|
||||
* Employ tool (or function) calling and schema constraints to guide and restrict model responses to predefined formats, increasing response predictability.
|
||||
|
||||
- Adjust prompts and model parameters (such as [temperature](https://ai.google.dev/gemini-api/docs/prompting-strategies)) for more or less deterministic responses. You can [find out more in the prompting strategies section](https://ai.google.dev/gemini-api/docs/prompting-strategies) of [ai.google.dev](https://ai.google.dev/).
|
||||
- Use the "human in the loop" strategy where a human verifies outputs before proceeding in a workflow. Build your application workflows to allow operators (humans or other models) to verify outputs and confirm key decisions.
|
||||
- Employ tool (or function) calling and schema constraints to guide and restrict model responses to predefined formats, increasing response predictability.
|
||||
|
||||
Even considering these strategies and techniques, sensible fallbacks should be incorporated in your application design. Follow existing standards of application resiliency. For example, it is not acceptable for an application to crash if a resource or API is not available. In that scenario, an error message is displayed to the user and, if applicable, options for next steps are also displayed. Building AI-powered applications requires the same consideration. Confirm that the response is aligned with the expected output and provide a "safe landing" in case it is not aligned by way of [graceful degradation](https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation). This also applies to API outages for LLM providers.
|
||||
|
||||
Consider this example: The LLM provider is not responding. A potential strategy to handle the outage is:
|
||||
* Save the response from the user to use in a retry scenario (now or at a later time)
|
||||
* Alert the user to the outage with an appropriate message that doesn't reveal sensitive information
|
||||
* Resume the conversation at a later time once the services are available again.
|
||||
|
||||
- Save the response from the user to use in a retry scenario (now or at a later time)
|
||||
- Alert the user to the outage with an appropriate message that doesn't reveal sensitive information
|
||||
- Resume the conversation at a later time once the services are available again.
|
||||
|
||||
## Next steps
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ This page discusses best practices for designing Angular applications that work
|
|||
## Accessibility attributes
|
||||
|
||||
<!-- TODO: add link once attribute binding guide is implemented -->
|
||||
|
||||
Building accessible web experience often involves setting [Accessible Rich Internet Applications \(ARIA\) attributes](https://web.dev/learn/accessibility/aria-html/) to provide semantic meaning where it might otherwise be missing.
|
||||
Use attribute binding template syntax to control the values of accessibility-related attributes.
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ When binding to ARIA attributes in Angular, you must use the `attr.` prefix. The
|
|||
<button [attr.aria-label]="myActionLabel">…</button>
|
||||
</docs-code>
|
||||
|
||||
NOTE: This syntax is only necessary for attribute *bindings*.
|
||||
NOTE: This syntax is only necessary for attribute _bindings_.
|
||||
Static ARIA attributes require no extra syntax.
|
||||
|
||||
<docs-code language="html">
|
||||
|
|
@ -32,6 +33,7 @@ Static ARIA attributes require no extra syntax.
|
|||
HELPFUL: By convention, HTML attributes use lowercase names \(`tabindex`\), while properties use camelCase names \(`tabIndex`\).
|
||||
|
||||
<!-- TODO: add link once attribute binding guide implemented -->
|
||||
|
||||
See the [Binding syntax guide](guide/templates) for more background on the difference between attributes and properties.
|
||||
|
||||
## Angular UI components
|
||||
|
|
@ -40,11 +42,11 @@ The [Angular Material](https://material.angular.dev) library, which is maintaine
|
|||
The [Component Development Kit (CDK)](https://material.angular.dev/cdk/categories) includes the `a11y` package that provides tools to support various areas of accessibility.
|
||||
For example:
|
||||
|
||||
* `LiveAnnouncer` is used to announce messages for screen-reader users using an `aria-live` region.
|
||||
See the W3C documentation for more information on [aria-live regions](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties#aria-live).
|
||||
- `LiveAnnouncer` is used to announce messages for screen-reader users using an `aria-live` region.
|
||||
See the W3C documentation for more information on [aria-live regions](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties#aria-live).
|
||||
|
||||
* The `cdkTrapFocus` directive traps Tab-key focus within an element.
|
||||
Use it to create accessible experience for components such as modal dialogs, where focus must be constrained.
|
||||
- The `cdkTrapFocus` directive traps Tab-key focus within an element.
|
||||
Use it to create accessible experience for components such as modal dialogs, where focus must be constrained.
|
||||
|
||||
For full details of these and other tools, see the [Angular CDK accessibility overview](https://material.angular.dev/cdk/a11y/overview).
|
||||
|
||||
|
|
@ -63,7 +65,7 @@ You can see examples of this pattern in Angular Material:
|
|||
|
||||
Sometimes using the appropriate native element requires a container element.
|
||||
For example, the native `<input>` element cannot have children, so any custom text entry components need to wrap an `<input>` with extra elements.
|
||||
By just including `<input>` in your custom component's template, it's impossible for your component's users to set arbitrary properties and attributes to the `<input>` element.
|
||||
By just including `<input>` in your custom component's template, it's impossible for your component's users to set arbitrary properties and attributes to the `<input>` element.
|
||||
Instead, create a container component that uses content projection to include the native control in the component's API.
|
||||
|
||||
You can see [`MatFormField`](https://material.angular.dev/components/form-field/overview) as an example of this pattern.
|
||||
|
|
@ -72,9 +74,9 @@ You can see [`MatFormField`](https://material.angular.dev/components/form-field/
|
|||
|
||||
The following example shows how to make a progress bar accessible by using host binding to control accessibility-related attributes.
|
||||
|
||||
* The component defines an accessibility-enabled element with both the standard HTML attribute `role`, and ARIA attributes.
|
||||
The ARIA attribute `aria-valuenow` is bound to the user's input.
|
||||
* In the template, the `aria-label` attribute ensures that the control is accessible to screen readers.
|
||||
- The component defines an accessibility-enabled element with both the standard HTML attribute `role`, and ARIA attributes.
|
||||
The ARIA attribute `aria-valuenow` is bound to the user's input.
|
||||
- In the template, the `aria-label` attribute ensures that the control is accessible to screen readers.
|
||||
|
||||
<docs-code-multifile>
|
||||
<docs-code
|
||||
|
|
@ -104,10 +106,10 @@ 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')
|
||||
if (mainHeader) {
|
||||
mainHeader.focus();
|
||||
}
|
||||
const mainHeader = document.querySelector('#main-content-header')
|
||||
if (mainHeader) {
|
||||
mainHeader.focus();
|
||||
}
|
||||
});
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -151,15 +153,15 @@ The following example shows how to apply the `active-page` class to active links
|
|||
|
||||
## More information
|
||||
|
||||
* [Accessibility - Google Web Fundamentals](https://developers.google.com/web/fundamentals/accessibility)
|
||||
* [ARIA specification and authoring practices](https://www.w3.org/TR/wai-aria)
|
||||
* [Material Design - Accessibility](https://material.io/design/usability/accessibility.html)
|
||||
* [Smashing Magazine](https://www.smashingmagazine.com/search/?q=accessibility)
|
||||
* [Inclusive Components](https://inclusive-components.design)
|
||||
* [Accessibility Resources and Code Examples](https://dequeuniversity.com/resources)
|
||||
* [W3C - Web Accessibility Initiative](https://www.w3.org/WAI/people-use-web)
|
||||
* [Rob Dodson A11ycasts](https://www.youtube.com/watch?v=HtTyRajRuyY)
|
||||
* [Angular ESLint](https://github.com/angular-eslint/angular-eslint#functionality) provides linting rules that can help you make sure your code meets accessibility standards.
|
||||
- [Accessibility - Google Web Fundamentals](https://developers.google.com/web/fundamentals/accessibility)
|
||||
- [ARIA specification and authoring practices](https://www.w3.org/TR/wai-aria)
|
||||
- [Material Design - Accessibility](https://material.io/design/usability/accessibility.html)
|
||||
- [Smashing Magazine](https://www.smashingmagazine.com/search/?q=accessibility)
|
||||
- [Inclusive Components](https://inclusive-components.design)
|
||||
- [Accessibility Resources and Code Examples](https://dequeuniversity.com/resources)
|
||||
- [W3C - Web Accessibility Initiative](https://www.w3.org/WAI/people-use-web)
|
||||
- [Rob Dodson A11ycasts](https://www.youtube.com/watch?v=HtTyRajRuyY)
|
||||
- [Angular ESLint](https://github.com/angular-eslint/angular-eslint#functionality) provides linting rules that can help you make sure your code meets accessibility standards.
|
||||
|
||||
<!-- vale Angular.Angular_Spelling = YES -->
|
||||
|
||||
|
|
@ -167,7 +169,7 @@ Books
|
|||
|
||||
<!-- vale Angular.Google_Quotes = NO -->
|
||||
|
||||
* "A Web for Everyone: Designing Accessible User Experiences," Sarah Horton and Whitney Quesenbery
|
||||
* "Inclusive Design Patterns," Heydon Pickering
|
||||
- "A Web for Everyone: Designing Accessible User Experiences," Sarah Horton and Whitney Quesenbery
|
||||
- "Inclusive Design Patterns," Heydon Pickering
|
||||
|
||||
<!-- vale Angular.Google_Quotes = YES -->
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ If you are confident that a part of the application is not affected by a state c
|
|||
|
||||
OnPush change detection instructs Angular to run change detection for a component subtree **only** when:
|
||||
|
||||
* The root component of the subtree receives new inputs as the result of a template binding. Angular compares the current and past value of the input with `==`.
|
||||
* Angular handles an event _(for example using event binding, output binding, or `@HostListener` )_ in the subtree's root component or any of its children whether they are using OnPush change detection or not.
|
||||
- The root component of the subtree receives new inputs as the result of a template binding. Angular compares the current and past value of the input with `==`.
|
||||
- Angular handles an event _(for example using event binding, output binding, or `@HostListener` )_ in the subtree's root component or any of its children whether they are using OnPush change detection or not.
|
||||
|
||||
You can set the change detection strategy of a component to `OnPush` in the `@Component` decorator:
|
||||
|
||||
|
|
@ -128,5 +128,5 @@ class event eventNode
|
|||
|
||||
## Edge cases
|
||||
|
||||
* **Modifying input properties in TypeScript code**. When you use an API like `@ViewChild` or `@ContentChild` to get a reference to a component in TypeScript and manually modify an `@Input` property, Angular will not automatically run change detection for OnPush components. If you need Angular to run change detection, you can inject `ChangeDetectorRef` in your component and call `changeDetectorRef.markForCheck()` to tell Angular to schedule a change detection.
|
||||
* **Modifying object references**. In case an input receives a mutable object as value and you modify the object but preserve the reference, Angular will not invoke change detection. That’s the expected behavior because the previous and the current value of the input point to the same reference.
|
||||
- **Modifying input properties in TypeScript code**. When you use an API like `@ViewChild` or `@ContentChild` to get a reference to a component in TypeScript and manually modify an `@Input` property, Angular will not automatically run change detection for OnPush components. If you need Angular to run change detection, you can inject `ChangeDetectorRef` in your component and call `changeDetectorRef.markForCheck()` to tell Angular to schedule a change detection.
|
||||
- **Modifying object references**. In case an input receives a mutable object as value and you modify the object but preserve the reference, Angular will not invoke change detection. That’s the expected behavior because the previous and the current value of the input point to the same reference.
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
On every change detection cycle, Angular synchronously:
|
||||
|
||||
* Evaluates all template expressions in all components, unless specified otherwise, based on that each component's detection strategy
|
||||
* Executes the `ngDoCheck`, `ngAfterContentChecked`, `ngAfterViewChecked`, and `ngOnChanges` lifecycle hooks.
|
||||
A single slow computation within a template or a lifecycle hook can slow down the entire change detection process because Angular runs the computations sequentially.
|
||||
- Evaluates all template expressions in all components, unless specified otherwise, based on that each component's detection strategy
|
||||
- Executes the `ngDoCheck`, `ngAfterContentChecked`, `ngAfterViewChecked`, and `ngOnChanges` lifecycle hooks.
|
||||
A single slow computation within a template or a lifecycle hook can slow down the entire change detection process because Angular runs the computations sequentially.
|
||||
|
||||
## Identifying slow computations
|
||||
|
||||
You can identify heavy computations with Angular DevTools’ profiler. In the performance timeline, click a bar to preview a particular change detection cycle. This displays a bar chart, which shows how long the framework spent in change detection for each component. When you click a component, you can preview how long Angular spent evaluating its template and lifecycle hooks.
|
||||
You can identify heavy computations with Angular DevTools’ profiler. In the performance timeline, click a bar to preview a particular change detection cycle. This displays a bar chart, which shows how long the framework spent in change detection for each component. When you click a component, you can preview how long Angular spent evaluating its template and lifecycle hooks.
|
||||
|
||||
<img alt="Angular DevTools profiler preview showing slow computation" src="assets/images/best-practices/runtime-performance/slow-computations.png">
|
||||
|
||||
|
|
@ -18,9 +18,9 @@ For example, in the preceding screenshot, the second recorded change detection c
|
|||
|
||||
Here are several techniques to remove slow computations:
|
||||
|
||||
* **Optimizing the underlying algorithm**. This is the recommended approach. If you can speed up the algorithm that is causing the problem, you can speed up the entire change detection mechanism.
|
||||
* **Caching using pure pipes**. You can move the heavy computation to a pure [pipe](guide/pipes). Angular reevaluates a pure pipe only if it detects that its inputs have changed, compared to the previous time Angular called it.
|
||||
* **Using memoization**. [Memoization](https://en.wikipedia.org/wiki/Memoization) is a similar technique to pure pipes, with the difference that pure pipes preserve only the last result from the computation where memoization could store multiple results.
|
||||
* **Avoid repaints/reflows in lifecycle hooks**. Certain [operations](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing/) cause the browser to either synchronously recalculate the layout of the page or re-render it. Since reflows and repaints are generally slow, you want to avoid performing them in every change detection cycle.
|
||||
- **Optimizing the underlying algorithm**. This is the recommended approach. If you can speed up the algorithm that is causing the problem, you can speed up the entire change detection mechanism.
|
||||
- **Caching using pure pipes**. You can move the heavy computation to a pure [pipe](guide/pipes). Angular reevaluates a pure pipe only if it detects that its inputs have changed, compared to the previous time Angular called it.
|
||||
- **Using memoization**. [Memoization](https://en.wikipedia.org/wiki/Memoization) is a similar technique to pure pipes, with the difference that pure pipes preserve only the last result from the computation where memoization could store multiple results.
|
||||
- **Avoid repaints/reflows in lifecycle hooks**. Certain [operations](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing/) cause the browser to either synchronously recalculate the layout of the page or re-render it. Since reflows and repaints are generally slow, you want to avoid performing them in every change detection cycle.
|
||||
|
||||
Pure pipes and memoization have different trade-offs. Pure pipes are an Angular built-in concept compared to memoization, which is a general software engineering practice for caching function results. The memory overhead of memoization could be significant if you invoke the heavy computation frequently with different arguments.
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
In some cases scheduled [tasks](https://developer.mozilla.org/docs/Web/API/HTML_DOM_API/Microtask_guide#tasks) or [microtasks](https://developer.mozilla.org/docs/Web/API/HTML_DOM_API/Microtask_guide#microtasks) don’t make any changes in the data model, which makes running change detection unnecessary. Common examples are:
|
||||
|
||||
* `requestAnimationFrame`, `setTimeout` or `setInterval`
|
||||
* Task or microtask scheduling by third-party libraries
|
||||
- `requestAnimationFrame`, `setTimeout` or `setInterval`
|
||||
- Task or microtask scheduling by third-party libraries
|
||||
|
||||
This section covers how to identify such conditions, and how to run code outside the Angular zone to avoid unnecessary change detection calls.
|
||||
|
||||
|
|
@ -26,11 +26,11 @@ import { Component, NgZone, OnInit } from '@angular/core';
|
|||
|
||||
@Component(...)
|
||||
class AppComponent implements OnInit {
|
||||
private ngZone = inject(NgZone);
|
||||
private ngZone = inject(NgZone);
|
||||
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => setInterval(pollForUpdates), 500);
|
||||
}
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => setInterval(pollForUpdates), 500);
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -44,13 +44,13 @@ import * as Plotly from 'plotly.js-dist-min';
|
|||
|
||||
@Component(...)
|
||||
class AppComponent implements OnInit {
|
||||
private ngZone = inject(NgZone);
|
||||
private ngZone = inject(NgZone);
|
||||
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
Plotly.newPlot('chart', data);
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
Plotly.newPlot('chart', data);
|
||||
});
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -66,18 +66,18 @@ import * as Plotly from 'plotly.js-dist-min';
|
|||
|
||||
@Component(...)
|
||||
class AppComponent implements OnInit {
|
||||
private ngZone = inject(NgZone);
|
||||
private ngZone = inject(NgZone);
|
||||
|
||||
plotlyClick = output<Plotly.PlotMouseEvent>();
|
||||
plotlyClick = output<Plotly.PlotMouseEvent>();
|
||||
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
this.createPlotly();
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
this.createPlotly();
|
||||
});
|
||||
}
|
||||
|
||||
private async createPlotly() {
|
||||
const plotly = await Plotly.newPlot('chart', data);
|
||||
private async createPlotly() {
|
||||
const plotly = await Plotly.newPlot('chart', data);
|
||||
|
||||
plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {
|
||||
// This handler will be called outside of the Angular zone because
|
||||
|
|
@ -86,7 +86,8 @@ class AppComponent implements OnInit {
|
|||
console.log(NgZone.isInAngularZone());
|
||||
this.plotlyClick.emit(event);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -98,26 +99,27 @@ import * as Plotly from 'plotly.js-dist-min';
|
|||
|
||||
@Component(...)
|
||||
class AppComponent implements OnInit {
|
||||
private ngZone = inject(NgZone);
|
||||
private ngZone = inject(NgZone);
|
||||
|
||||
plotlyClick = output<Plotly.PlotMouseEvent>();
|
||||
plotlyClick = output<Plotly.PlotMouseEvent>();
|
||||
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
this.createPlotly();
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
this.ngZone.runOutsideAngular(() => {
|
||||
this.createPlotly();
|
||||
});
|
||||
}
|
||||
|
||||
private async createPlotly() {
|
||||
const plotly = await Plotly.newPlot('chart', data);
|
||||
private async createPlotly() {
|
||||
const plotly = await Plotly.newPlot('chart', data);
|
||||
|
||||
plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {
|
||||
this.ngZone.run(() => {
|
||||
this.plotlyClick.emit(event);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
The scenario of dispatching events outside of the Angular zone may also arise. It's important to remember that triggering change detection (for example, manually) may result in the creation/update of views outside of the Angular zone.
|
||||
The scenario of dispatching events outside of the Angular zone may also arise. It's important to remember that triggering change detection (for example, manually) may result in the creation/update of views outside of the Angular zone.
|
||||
|
|
|
|||
|
|
@ -112,10 +112,10 @@ When in doubt, go with the approach that leads to smaller files.
|
|||
|
||||
Prefer using the `inject` function over injecting constructor parameters. The `inject` function works the same way as constructor parameter injection, but offers several style advantages:
|
||||
|
||||
* `inject` is generally more readable, especially when a class injects many dependencies.
|
||||
* It's more syntactically straightforward to add comments to injected dependencies
|
||||
* `inject` offers better type inference.
|
||||
* When targeting ES2022+ with [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields), you can avoid separating field declaration and initialization when fields read on injected dependencies.
|
||||
- `inject` is generally more readable, especially when a class injects many dependencies.
|
||||
- It's more syntactically straightforward to add comments to injected dependencies
|
||||
- `inject` offers better type inference.
|
||||
- When targeting ES2022+ with [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields), you can avoid separating field declaration and initialization when fields read on injected dependencies.
|
||||
|
||||
[You can refactor existing code to `inject` with an automatic tool](reference/migrations/inject-function).
|
||||
|
||||
|
|
|
|||
|
|
@ -6,20 +6,20 @@ 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 —including support and deprecation practices, as well as the release schedule— 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.
|
||||
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.
|
||||
|
||||
## Getting notified of new releases
|
||||
|
||||
To be notified when new releases are available, follow [@angular](https://x.com/angular "@angular on X") on X (formerly Twitter) or subscribe to the [Angular blog](https://blog.angular.dev "Angular blog").
|
||||
To be notified when new releases are available, follow [@angular](https://x.com/angular '@angular on X') on X (formerly Twitter) or subscribe to the [Angular blog](https://blog.angular.dev 'Angular blog').
|
||||
|
||||
## Learning about new features
|
||||
|
||||
What's new? What's changed? We share the most important things you need to know on the Angular blog in [release announcements]( https://blog.angular.dev/ "Angular blog - release announcements").
|
||||
What's new? What's changed? We share the most important things you need to know on the Angular blog in [release announcements](https://blog.angular.dev/ 'Angular blog - release announcements').
|
||||
|
||||
To review a complete list of changes, organized by version, see the [Angular change log](https://github.com/angular/angular/blob/main/CHANGELOG.md "Angular change log").
|
||||
To review a complete list of changes, organized by version, see the [Angular change log](https://github.com/angular/angular/blob/main/CHANGELOG.md 'Angular change log').
|
||||
|
||||
## Checking your version of Angular
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ To check your application's version of Angular use the `ng version` command from
|
|||
|
||||
## Finding the current version of Angular
|
||||
|
||||
The most recent stable released version of Angular appears [on npm](https://www.npmjs.com/package/@angular/core "Angular on npm") under "Version." For example, `16.2.4`.
|
||||
The most recent stable released version of Angular appears [on npm](https://www.npmjs.com/package/@angular/core 'Angular on npm') under "Version." For example, `16.2.4`.
|
||||
|
||||
You can also find the most current version of Angular by using the CLI command [`ng update`](cli/update).
|
||||
By default, [`ng update`](cli/update)(without additional arguments) lists the updates that are available to you.
|
||||
|
|
@ -43,22 +43,22 @@ It also includes troubleshooting information and any recommended manual changes
|
|||
For simple updates, the CLI command [`ng update`](cli/update) is all you need.
|
||||
Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version.
|
||||
|
||||
[Angular Versioning and Releases](reference/releases#versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based on a release's version number.
|
||||
[Angular Versioning and Releases](reference/releases#versioning 'Angular Release Practices, Versioning') describes the level of change that you can expect based on a release's version number.
|
||||
It also describes supported update paths.
|
||||
|
||||
## Resource summary
|
||||
|
||||
* Release announcements:
|
||||
[Angular blog - release announcements](https://blog.angular.dev/ "Angular blog announcements about recent releases")
|
||||
- Release announcements:
|
||||
[Angular blog - release announcements](https://blog.angular.dev/ 'Angular blog announcements about recent releases')
|
||||
|
||||
* Release details:
|
||||
[Angular change log](https://github.com/angular/angular/blob/main/CHANGELOG.md "Angular change log")
|
||||
- Release details:
|
||||
[Angular change log](https://github.com/angular/angular/blob/main/CHANGELOG.md 'Angular change log')
|
||||
|
||||
* Update instructions:
|
||||
[Angular Update Guide](update-guide)
|
||||
- Update instructions:
|
||||
[Angular Update Guide](update-guide)
|
||||
|
||||
* Update command reference:
|
||||
[Angular CLI `ng update` command reference](cli/update)
|
||||
- Update command reference:
|
||||
[Angular CLI `ng update` command reference](cli/update)
|
||||
|
||||
* Versioning, release, support, and deprecation practices:
|
||||
[Angular versioning and releases](reference/releases "Angular versioning and releases")
|
||||
- Versioning, release, support, and deprecation practices:
|
||||
[Angular versioning and releases](reference/releases 'Angular versioning and releases')
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ If the current working directory is not the right place for your project, you ca
|
|||
|
||||
## Workspaces and project files
|
||||
|
||||
The [ng new](cli/new) command creates an *Angular workspace* folder and generates a new application skeleton.
|
||||
The [ng new](cli/new) command creates an _Angular workspace_ folder and generates a new application skeleton.
|
||||
A workspace can contain multiple applications and libraries.
|
||||
The initial application created by the [ng new](cli/new) command is at the top level of the workspace.
|
||||
When you generate an additional application or library in a workspace, it goes into a `projects/` subfolder.
|
||||
|
|
@ -65,7 +65,7 @@ You can edit the generated files directly, or add to and modify them using CLI c
|
|||
Use the [ng generate](cli/generate) command to add new files for additional components and services, and code for new pipes, directives, and so on.
|
||||
Commands such as [add](cli/add) and [generate](cli/generate), which create or operate on applications and libraries, must be executed from within a workspace or project folder.
|
||||
|
||||
* See more about the [Workspace file structure](guide/file-structure).
|
||||
- See more about the [Workspace file structure](guide/file-structure).
|
||||
|
||||
### Workspace and project configuration
|
||||
|
||||
|
|
@ -81,32 +81,32 @@ Option names in the configuration file must use [camelCase](guide/glossary#case-
|
|||
|
||||
</div>
|
||||
|
||||
* See more about [Workspace Configuration](guide/workspace-config).
|
||||
- See more about [Workspace Configuration](guide/workspace-config).
|
||||
|
||||
## CLI command-language syntax
|
||||
|
||||
Command syntax is shown as follows:
|
||||
|
||||
`ng` *<command-name>* *<required-arg>* [*optional-arg*] `[options]`
|
||||
`ng` _<command-name>_ _<required-arg>_ [*optional-arg*] `[options]`
|
||||
|
||||
* Most commands, and some options, have aliases.
|
||||
Aliases are shown in the syntax statement for each command.
|
||||
- Most commands, and some options, have aliases.
|
||||
Aliases are shown in the syntax statement for each command.
|
||||
|
||||
* Option names are prefixed with a double dash \(`--`\) characters.
|
||||
Option aliases are prefixed with a single dash \(`-`\) character.
|
||||
Arguments are not prefixed.
|
||||
For example:
|
||||
- Option names are prefixed with a double dash \(`--`\) characters.
|
||||
Option aliases are prefixed with a single dash \(`-`\) character.
|
||||
Arguments are not prefixed.
|
||||
For example:
|
||||
|
||||
<code-example format="shell" language="shell">
|
||||
<code-example format="shell" language="shell">
|
||||
|
||||
ng build my-app -c production
|
||||
ng build my-app -c production
|
||||
|
||||
</code-example>
|
||||
</code-example>
|
||||
|
||||
* Typically, the name of a generated artifact can be given as an argument to the command or specified with the `--name` option.
|
||||
- Typically, the name of a generated artifact can be given as an argument to the command or specified with the `--name` option.
|
||||
|
||||
* Arguments and option names must be given in [dash-case](guide/glossary#case-types).
|
||||
For example: `--my-option-name`
|
||||
- Arguments and option names must be given in [dash-case](guide/glossary#case-types).
|
||||
For example: `--my-option-name`
|
||||
|
||||
### Boolean options
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ Options that specify files can be given as absolute paths, or as paths relative
|
|||
### Schematics
|
||||
|
||||
The [ng generate](cli/generate) and [ng add](cli/add) commands take, as an argument, the artifact or library to be generated or added to the current project.
|
||||
In addition to any general options, each artifact or library defines its own options in a *schematic*.
|
||||
In addition to any general options, each artifact or library defines its own options in a _schematic_.
|
||||
Schematic options are supplied to the command in the same format as immediate command options.
|
||||
|
||||
<!-- links -->
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ This page explores the **rare use cases** when you need a custom build pipeline
|
|||
|
||||
There are some niche use cases when you may want to maintain a custom build pipeline. For example:
|
||||
|
||||
* You have an existing app using a different toolchain and you’d like to add Angular to it
|
||||
* You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation)
|
||||
* You’d like to create an short-lived experiment using your favorite build tool
|
||||
- You have an existing app using a different toolchain and you’d like to add Angular to it
|
||||
- You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation)
|
||||
- You’d like to create an short-lived experiment using your favorite build tool
|
||||
|
||||
## What are the options?
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ The `outputFromObservable` lets you create a component or directive output that
|
|||
import {Directive} from '@angular/core';
|
||||
import {outputFromObservable} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Directive({/*...*/})
|
||||
@Directive({/_..._/})
|
||||
class Draggable {
|
||||
pointerMoves$: Observable<PointerMovements> = listenToPointerMoves();
|
||||
|
||||
// Whenever `pointerMoves$` emits, the `pointerMove` event fires.
|
||||
pointerMove = outputFromObservable(this.pointerMoves$);
|
||||
pointerMoves$: Observable<PointerMovements> = listenToPointerMoves();
|
||||
|
||||
// Whenever `pointerMoves$` emits, the `pointerMove` event fires.
|
||||
pointerMove = outputFromObservable(this.pointerMoves$);
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -34,17 +34,17 @@ The `outputToObservable` function lets you create an RxJS observable from a comp
|
|||
<docs-code language="ts" highlight="[11]">
|
||||
import {outputToObservable} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component(/*...*/)
|
||||
@Component(/_..._/)
|
||||
class CustomSlider {
|
||||
valueChange = output<number>();
|
||||
valueChange = output<number>();
|
||||
}
|
||||
|
||||
// Instance reference to `CustomSlider`.
|
||||
const slider: CustomSlider = createSlider();
|
||||
|
||||
outputToObservable(slider.valueChange) // Observable<number>
|
||||
.pipe(...)
|
||||
.subscribe(...);
|
||||
.pipe(...)
|
||||
.subscribe(...);
|
||||
</docs-code>
|
||||
|
||||
HELPFUL: Consider using the `subscribe` method on `OutputRef` directly if it meets your needs.
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@ The `SwUpdate` service gives you access to events that indicate when the service
|
|||
|
||||
The `SwUpdate` service supports three separate operations:
|
||||
|
||||
* Receiving notifications when an updated version is *detected* on the server, *installed and ready* to be used locally or when an *installation fails*.
|
||||
* Asking the service worker to check the server for new updates.
|
||||
* Asking the service worker to activate the latest version of the application for the current tab.
|
||||
- Receiving notifications when an updated version is _detected_ on the server, _installed and ready_ to be used locally or when an _installation fails_.
|
||||
- Asking the service worker to check the server for new updates.
|
||||
- Asking the service worker to activate the latest version of the application for the current tab.
|
||||
|
||||
### Version updates
|
||||
|
||||
The `versionUpdates` is an `Observable` property of `SwUpdate` and emits five event types:
|
||||
|
||||
| Event types | Details |
|
||||
|:--- |:--- |
|
||||
| `VersionDetectedEvent` | Emitted when the service worker has detected a new version of the app on the server and is about to start downloading it. |
|
||||
| `NoNewVersionDetectedEvent` | Emitted when the service worker has checked the version of the app on the server and did not find a new version. |
|
||||
| `VersionReadyEvent` | Emitted when a new version of the app is available to be activated by clients. It may be used to notify the user of an available update or prompt them to refresh the page. |
|
||||
| `VersionInstallationFailedEvent` | Emitted when the installation of a new version failed. It may be used for logging/monitoring purposes. |
|
||||
| Event types | Details |
|
||||
| :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `VersionDetectedEvent` | Emitted when the service worker has detected a new version of the app on the server and is about to start downloading it. |
|
||||
| `NoNewVersionDetectedEvent` | Emitted when the service worker has checked the version of the app on the server and did not find a new version. |
|
||||
| `VersionReadyEvent` | Emitted when a new version of the app is available to be activated by clients. It may be used to notify the user of an available update or prompt them to refresh the page. |
|
||||
| `VersionInstallationFailedEvent` | Emitted when the installation of a new version failed. It may be used for logging/monitoring purposes. |
|
||||
| `VersionFailedEvent` | Emitted when a version encounters a critical failure (such as broken hash errors) that affects all clients using that version. Provides error details for debugging and transparency. |
|
||||
|
||||
<docs-code header="log-update.service.ts" path="adev/src/content/examples/service-worker-getting-started/src/app/log-update.service.ts" visibleRegion="sw-update"/>
|
||||
|
|
@ -73,23 +73,23 @@ In some cases, the version of the application used by the service worker to serv
|
|||
For example, imagine the following scenario:
|
||||
|
||||
1. A user opens the application for the first time and the service worker caches the latest version of the application.
|
||||
Assume the application's cached assets include `index.html`, `main.<main-hash-1>.js` and `lazy-chunk.<lazy-hash-1>.js`.
|
||||
Assume the application's cached assets include `index.html`, `main.<main-hash-1>.js` and `lazy-chunk.<lazy-hash-1>.js`.
|
||||
|
||||
1. The user closes the application and does not open it for a while.
|
||||
1. After some time, a new version of the application is deployed to the server.
|
||||
This newer version includes the files `index.html`, `main.<main-hash-2>.js` and `lazy-chunk.<lazy-hash-2>.js`.
|
||||
This newer version includes the files `index.html`, `main.<main-hash-2>.js` and `lazy-chunk.<lazy-hash-2>.js`.
|
||||
|
||||
IMPORTANT: The hashes are different now, because the content of the files changed. The old version is no longer available on the server.
|
||||
|
||||
1. In the meantime, the user's browser decides to evict `lazy-chunk.<lazy-hash-1>.js` from its cache.
|
||||
Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space.
|
||||
Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space.
|
||||
|
||||
1. The user opens the application again.
|
||||
The service worker serves the latest version known to it at this point, namely the old version (`index.html` and `main.<main-hash-1>.js`).
|
||||
The service worker serves the latest version known to it at this point, namely the old version (`index.html` and `main.<main-hash-1>.js`).
|
||||
|
||||
1. At some later point, the application requests the lazy bundle, `lazy-chunk.<lazy-hash-1>.js`.
|
||||
1. The service worker is unable to find the asset in the cache (remember that the browser evicted it).
|
||||
Nor is it able to retrieve it from the server (because the server now only has `lazy-chunk.<lazy-hash-2>.js` from the newer version).
|
||||
Nor is it able to retrieve it from the server (because the server now only has `lazy-chunk.<lazy-hash-2>.js` from the newer version).
|
||||
|
||||
In the preceding scenario, the service worker is not able to serve an asset that would normally be cached.
|
||||
That particular application version is broken and there is no way to fix the state of the client without reloading the page.
|
||||
|
|
|
|||
|
|
@ -14,36 +14,38 @@ To create a custom service worker that extends Angular's functionality:
|
|||
importScripts('./ngsw-worker.js');
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
// Add custom notification click handler
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
console.log('Custom notification click handler');
|
||||
console.log('Notification details:', event.notification);
|
||||
|
||||
// Add custom notification click handler
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
console.log('Custom notification click handler');
|
||||
console.log('Notification details:', event.notification);
|
||||
|
||||
// Handle notification click - open URL if provided
|
||||
if (clients.openWindow && event.notification.data.url) {
|
||||
event.waitUntil(clients.openWindow(event.notification.data.url));
|
||||
console.log('Opening URL:', event.notification.data.url);
|
||||
}
|
||||
});
|
||||
|
||||
// Add custom background sync handler
|
||||
self.addEventListener('sync', (event) => {
|
||||
console.log('Custom background sync handler');
|
||||
|
||||
});
|
||||
|
||||
// Add custom background sync handler
|
||||
self.addEventListener('sync', (event) => {
|
||||
console.log('Custom background sync handler');
|
||||
|
||||
if (event.tag === 'background-sync') {
|
||||
event.waitUntil(doBackgroundSync());
|
||||
}
|
||||
});
|
||||
|
||||
function doBackgroundSync() {
|
||||
// Implement your background sync logic here
|
||||
return fetch('https://example.com/api/sync')
|
||||
.then(response => response.json())
|
||||
.then(data => console.log('Background sync completed:', data))
|
||||
.catch(error => console.error('Background sync failed:', error));
|
||||
}
|
||||
});
|
||||
|
||||
function doBackgroundSync() {
|
||||
// Implement your background sync logic here
|
||||
return fetch('https://example.com/api/sync')
|
||||
.then(response => response.json())
|
||||
.then(data => console.log('Background sync completed:', data))
|
||||
.catch(error => console.error('Background sync failed:', error));
|
||||
}
|
||||
})();
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -53,23 +55,23 @@ importScripts('./ngsw-worker.js');
|
|||
<docs-code language="json">
|
||||
|
||||
{
|
||||
"projects": {
|
||||
"your-app": {
|
||||
"architect": {
|
||||
"build": {
|
||||
"options": {
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
},
|
||||
"app/src/custom-sw.js"
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"projects": {
|
||||
"your-app": {
|
||||
"architect": {
|
||||
"build": {
|
||||
"options": {
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
},
|
||||
"app/src/custom-sw.js"
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -82,12 +84,12 @@ import { ApplicationConfig, isDevMode } from '@angular/core';
|
|||
import { provideServiceWorker } from '@angular/service-worker';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideServiceWorker('custom-sw.js', {
|
||||
enabled: !isDevMode(),
|
||||
registrationStrategy: 'registerWhenStable:30000'
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
provideServiceWorker('custom-sw.js', {
|
||||
enabled: !isDevMode(),
|
||||
registrationStrategy: 'registerWhenStable:30000'
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -107,5 +109,5 @@ When extending the Angular service worker:
|
|||
Custom service workers are commonly used for:
|
||||
|
||||
- **Push notifications**: Handle incoming push messages and display notifications
|
||||
- **Background sync**: Sync data when the network connection is restored
|
||||
- **Background sync**: Sync data when the network connection is restored
|
||||
- **Custom navigation**: Handle special routing or offline page scenarios
|
||||
|
|
|
|||
|
|
@ -60,17 +60,17 @@ If necessary, the service worker enters a safe mode where requests fall back on
|
|||
|
||||
Hash mismatches can occur for a variety of reasons:
|
||||
|
||||
* Caching layers between the origin server and the end user could serve stale content
|
||||
* A non-atomic deployment could result in the Angular service worker having visibility of partially updated content
|
||||
* Errors during the build process could result in updated resources without `ngsw.json` being updated.
|
||||
The reverse could also happen resulting in an updated `ngsw.json` without updated resources.
|
||||
- Caching layers between the origin server and the end user could serve stale content
|
||||
- A non-atomic deployment could result in the Angular service worker having visibility of partially updated content
|
||||
- Errors during the build process could result in updated resources without `ngsw.json` being updated.
|
||||
The reverse could also happen resulting in an updated `ngsw.json` without updated resources.
|
||||
|
||||
#### Unhashed content
|
||||
|
||||
The only resources that have hashes in the `ngsw.json` manifest are resources that were present in the `dist` directory at the time the manifest was built.
|
||||
Other resources, especially those loaded from CDNs, have content that is unknown at build time or are updated more frequently than the application is deployed.
|
||||
|
||||
If the Angular service worker does not have a hash to verify a resource is valid, it still caches its contents. At the same time, it honors the HTTP caching headers by using a policy of *stale while revalidate*.
|
||||
If the Angular service worker does not have a hash to verify a resource is valid, it still caches its contents. At the same time, it honors the HTTP caching headers by using a policy of _stale while revalidate_.
|
||||
The Angular service worker continues to serve a resource even after its HTTP caching headers indicate
|
||||
that it is no longer valid. At the same time, it attempts to refresh the expired resource in the background.
|
||||
This way, broken unhashed resources do not remain in the cache beyond their configured lifetimes.
|
||||
|
|
@ -88,15 +88,15 @@ IMPORTANT: This guarantee is **stronger** than that provided by the normal web d
|
|||
|
||||
The Angular service worker might change the version of a running application under error conditions such as:
|
||||
|
||||
* The current version becomes non-valid due to a failed hash.
|
||||
* An unrelated error causes the service worker to enter safe mode and deactivates it temporarily.
|
||||
- The current version becomes non-valid due to a failed hash.
|
||||
- An unrelated error causes the service worker to enter safe mode and deactivates it temporarily.
|
||||
|
||||
The Angular service worker cleans up application versions when no tab is using them.
|
||||
|
||||
Other reasons the Angular service worker might change the version of a running application are normal events:
|
||||
|
||||
* The page is reloaded/refreshed.
|
||||
* The page requests an update be immediately activated using the `SwUpdate` service.
|
||||
- The page is reloaded/refreshed.
|
||||
- The page requests an update be immediately activated using the `SwUpdate` service.
|
||||
|
||||
### Service worker updates
|
||||
|
||||
|
|
@ -155,7 +155,8 @@ 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)
|
||||
|
||||
- init post-load (update, cleanup)
|
||||
|
||||
Debug log:
|
||||
|
||||
|
|
@ -175,10 +176,10 @@ Driver state: NORMAL ((nominal))
|
|||
|
||||
There are two possible degraded states:
|
||||
|
||||
| Degraded states | Details |
|
||||
|:--- |:--- |
|
||||
| Degraded states | Details |
|
||||
| :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `EXISTING_CLIENTS_ONLY` | The service worker does not have a clean copy of the latest known version of the application. Older cached versions are safe to use, so existing tabs continue to run from cache, but new loads of the application will be served from the network. The service worker will try to recover from this state when a new version of the application is detected and installed. This happens when a new `ngsw.json` is available. |
|
||||
| `SAFE_MODE` | The service worker cannot guarantee the safety of using cached data. Either an unexpected error occurred or all cached versions are invalid. All traffic will be served from the network, running as little service worker code as possible. |
|
||||
| `SAFE_MODE` | The service worker cannot guarantee the safety of using cached data. Either an unexpected error occurred or all cached versions are invalid. All traffic will be served from the network, running as little service worker code as possible. |
|
||||
|
||||
In both cases, the parenthetical annotation provides the
|
||||
error that caused the service worker to enter the degraded state.
|
||||
|
|
@ -232,7 +233,8 @@ 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)
|
||||
|
||||
- init post-load (update, cleanup)
|
||||
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -259,13 +261,13 @@ Errors that occur within the service worker are logged here.
|
|||
Browsers such as Chrome provide developer tools for interacting with service workers.
|
||||
Such tools can be powerful when used properly, but there are a few things to keep in mind.
|
||||
|
||||
* When using developer tools, the service worker is kept running in the background and never restarts.
|
||||
This can cause behavior with Dev Tools open to differ from behavior a user might experience.
|
||||
- When using developer tools, the service worker is kept running in the background and never restarts.
|
||||
This can cause behavior with Dev Tools open to differ from behavior a user might experience.
|
||||
|
||||
* If you look in the Cache Storage viewer, the cache is frequently out of date.
|
||||
Right-click the Cache Storage title and refresh the caches.
|
||||
- If you look in the Cache Storage viewer, the cache is frequently out of date.
|
||||
Right-click the Cache Storage title and refresh the caches.
|
||||
|
||||
* Stopping and starting the service worker in the Service Worker pane checks for updates
|
||||
- Stopping and starting the service worker in the Service Worker pane checks for updates
|
||||
|
||||
## Service worker safety
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Service worker-based caching is thus completely programmable and doesn't rely on
|
|||
|
||||
Unlike the other scripts that make up an application, such as the Angular application bundle, the service worker is preserved after the user closes the tab.
|
||||
The next time that browser loads the application, the service worker loads first, and can intercept every request for resources to load the application.
|
||||
If the service worker is designed to do so, it can *completely satisfy the loading of the application, without the need for the network*.
|
||||
If the service worker is designed to do so, it can _completely satisfy the loading of the application, without the need for the network_.
|
||||
|
||||
Even across a fast reliable network, round-trip delays can introduce significant latency when loading the application.
|
||||
Using a service worker to reduce dependency on the network can significantly improve the user experience.
|
||||
|
|
@ -28,22 +28,22 @@ Angular's service worker is designed to optimize the end user experience of usin
|
|||
|
||||
To achieve this, the Angular service worker follows these guidelines:
|
||||
|
||||
* Caching an application is like installing a native application.
|
||||
The application is cached as one unit, and all files update together.
|
||||
- Caching an application is like installing a native application.
|
||||
The application is cached as one unit, and all files update together.
|
||||
|
||||
* A running application continues to run with the same version of all files.
|
||||
It does not suddenly start receiving cached files from a newer version, which are likely incompatible.
|
||||
- A running application continues to run with the same version of all files.
|
||||
It does not suddenly start receiving cached files from a newer version, which are likely incompatible.
|
||||
|
||||
* When users refresh the application, they see the latest fully cached version.
|
||||
New tabs load the latest cached code.
|
||||
- When users refresh the application, they see the latest fully cached version.
|
||||
New tabs load the latest cached code.
|
||||
|
||||
* Updates happen in the background, relatively quickly after changes are published.
|
||||
The previous version of the application is served until an update is installed and ready.
|
||||
- Updates happen in the background, relatively quickly after changes are published.
|
||||
The previous version of the application is served until an update is installed and ready.
|
||||
|
||||
* The service worker conserves bandwidth when possible.
|
||||
Resources are only downloaded if they've changed.
|
||||
- The service worker conserves bandwidth when possible.
|
||||
Resources are only downloaded if they've changed.
|
||||
|
||||
To support these behaviors, the Angular service worker loads a *manifest* file from the server.
|
||||
To support these behaviors, the Angular service worker loads a _manifest_ file from the server.
|
||||
The file, called `ngsw.json` (not to be confused with the [web app manifest](https://developer.mozilla.org/docs/Web/Manifest)), describes the resources to cache and includes hashes of every file's contents.
|
||||
When an update to the application is deployed, the contents of the manifest change, informing the service worker that a new version of the application should be downloaded and cached.
|
||||
This manifest is generated from a CLI-generated configuration file called `ngsw-config.json`.
|
||||
|
|
@ -60,7 +60,7 @@ For service workers to be registered, the application must be accessed over HTTP
|
|||
Browsers ignore service workers on pages that are served over an insecure connection.
|
||||
The reason is that service workers are quite powerful, so extra care is needed to ensure the service worker script has not been tampered with.
|
||||
|
||||
There is one exception to this rule: to make local development more straightforward, browsers do *not* require a secure connection when accessing an application on `localhost`.
|
||||
There is one exception to this rule: to make local development more straightforward, browsers do _not_ require a secure connection when accessing an application on `localhost`.
|
||||
|
||||
### Browser support
|
||||
|
||||
|
|
@ -71,9 +71,9 @@ Browsers like IE and Opera Mini do not support service workers.
|
|||
If the user is accessing your application with a browser that does not support service workers, the service worker is not registered and related behavior such as offline cache management and push notifications does not happen.
|
||||
More specifically:
|
||||
|
||||
* The browser does not download the service worker script and the `ngsw.json` manifest file
|
||||
* Active attempts to interact with the service worker, such as calling `SwUpdate.checkForUpdate()`, return rejected promises
|
||||
* The observable events of related services, such as `SwUpdate.available`, are not triggered
|
||||
- The browser does not download the service worker script and the `ngsw.json` manifest file
|
||||
- Active attempts to interact with the service worker, such as calling `SwUpdate.checkForUpdate()`, return rejected promises
|
||||
- The observable events of related services, such as `SwUpdate.available`, are not triggered
|
||||
|
||||
It is highly recommended that you ensure that your application works even without service worker support in the browser.
|
||||
Although an unsupported browser ignores service worker caching, it still reports errors if the application attempts to interact with the service worker.
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@ This is especially useful for when there are no open clients when a notification
|
|||
<docs-code language="json">
|
||||
|
||||
{
|
||||
"notification": {
|
||||
"title": "New Notification!",
|
||||
"data": {
|
||||
"onActionClick": {
|
||||
"default": {"operation": "openWindow", "url": "foo"}
|
||||
}
|
||||
}
|
||||
}
|
||||
"notification": {
|
||||
"title": "New Notification!",
|
||||
"data": {
|
||||
"onActionClick": {
|
||||
"default": {"operation": "openWindow", "url": "foo"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -42,12 +42,12 @@ This is especially useful for when there are no open clients when a notification
|
|||
|
||||
The Angular service worker supports the following operations:
|
||||
|
||||
| Operations | Details |
|
||||
|:--- |:--- |
|
||||
| Operations | Details |
|
||||
| :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `openWindow` | Opens a new tab at the specified URL. |
|
||||
| `focusLastFocusedOrOpen` | Focuses the last focused client. If there is no client open, then it opens a new tab at the specified URL. |
|
||||
| `navigateLastFocusedOrOpen` | Focuses the last focused client and navigates it to the specified URL. If there is no client open, then it opens a new tab at the specified URL. |
|
||||
| `sendRequest` | Send a simple GET request to the specified URL. |
|
||||
| `sendRequest` | Send a simple GET request to the specified URL. |
|
||||
|
||||
IMPORTANT: URLs are resolved relative to the service worker's registration scope.<br />If an `onActionClick` item does not define a `url`, then the service worker's registration scope is used.
|
||||
|
||||
|
|
@ -63,25 +63,25 @@ In addition, using the `onActionClick` property on the `data` object, you can ti
|
|||
<docs-code language="typescript">
|
||||
|
||||
{
|
||||
"notification": {
|
||||
"title": "New Notification!",
|
||||
"actions": [
|
||||
{"action": "foo", "title": "Open new tab"},
|
||||
{"action": "bar", "title": "Focus last"},
|
||||
{"action": "baz", "title": "Navigate last"},
|
||||
{"action": "qux", "title": "Send request in the background"},
|
||||
{"action": "other", "title": "Just notify existing clients"}
|
||||
],
|
||||
"data": {
|
||||
"onActionClick": {
|
||||
"default": {"operation": "openWindow"},
|
||||
"foo": {"operation": "openWindow", "url": "/absolute/path"},
|
||||
"bar": {"operation": "focusLastFocusedOrOpen", "url": "relative/path"},
|
||||
"baz": {"operation": "navigateLastFocusedOrOpen", "url": "https://other.domain.com/"},
|
||||
"qux": {"operation": "sendRequest", "url": "https://yet.another.domain.com/"}
|
||||
}
|
||||
}
|
||||
}
|
||||
"notification": {
|
||||
"title": "New Notification!",
|
||||
"actions": [
|
||||
{"action": "foo", "title": "Open new tab"},
|
||||
{"action": "bar", "title": "Focus last"},
|
||||
{"action": "baz", "title": "Navigate last"},
|
||||
{"action": "qux", "title": "Send request in the background"},
|
||||
{"action": "other", "title": "Just notify existing clients"}
|
||||
],
|
||||
"data": {
|
||||
"onActionClick": {
|
||||
"default": {"operation": "openWindow"},
|
||||
"foo": {"operation": "openWindow", "url": "/absolute/path"},
|
||||
"bar": {"operation": "focusLastFocusedOrOpen", "url": "relative/path"},
|
||||
"baz": {"operation": "navigateLastFocusedOrOpen", "url": "https://other.domain.com/"},
|
||||
"qux": {"operation": "sendRequest", "url": "https://yet.another.domain.com/"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</docs-code>
|
||||
|
|
|
|||
|
|
@ -27,34 +27,34 @@ ng generate web-worker app
|
|||
The command performs the following actions.
|
||||
|
||||
1. Configures your project to use web workers, if it isn't already.
|
||||
1. Adds the following scaffold code to `src/app/app.worker.ts` to receive messages.
|
||||
1. Adds the following scaffold code to `src/app/app.worker.ts` to receive messages.
|
||||
|
||||
<docs-code language="typescript" header="src/app/app.worker.ts">
|
||||
<docs-code language="typescript" header="src/app/app.worker.ts">
|
||||
|
||||
addEventListener('message', ({ data }) => {
|
||||
const response = `worker response to ${data}`;
|
||||
postMessage(response);
|
||||
});
|
||||
addEventListener('message', ({ data }) => {
|
||||
const response = `worker response to ${data}`;
|
||||
postMessage(response);
|
||||
});
|
||||
|
||||
</docs-code>
|
||||
</docs-code>
|
||||
|
||||
1. Adds the following scaffold code to `src/app/app.component.ts` to use the worker.
|
||||
|
||||
<docs-code language="typescript" header="src/app/app.component.ts">
|
||||
<docs-code language="typescript" header="src/app/app.component.ts">
|
||||
|
||||
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.postMessage('hello');
|
||||
} else {
|
||||
// Web workers are not supported in this environment.
|
||||
// You should add a fallback so that your program still executes correctly.
|
||||
}
|
||||
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.postMessage('hello');
|
||||
} else {
|
||||
// Web workers are not supported in this environment.
|
||||
// You should add a fallback so that your program still executes correctly.
|
||||
}
|
||||
|
||||
</docs-code>
|
||||
</docs-code>
|
||||
|
||||
After you create this initial scaffold, you must refactor your code to use the web worker by sending messages to and from the worker.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||

|
||||

|
||||
|
||||
# Angular v21: The Adventure Begins
|
||||
|
||||
## Save the Date November 20, 2025
|
||||
|
||||
<docs-pill-row>
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
Join the Angular team this November for a brand new release adventure. With modern AI tooling, performance updates and more, Angular v21 delivers fantastic new features to improve your developer experience. Whether you’re creating AI-powered apps or scalable enterprise applications, there has never been a better time to build with Angular.
|
||||
|
||||
🔥 What's coming in v21
|
||||
|
||||
- New Angular MCP Server tools to improve AI-powered workflows and code generation
|
||||
- Your first look at Signal Forms, our new streamlined, signal-based approach to forms in Angular
|
||||
- Exciting new details about the Angular ARIA package
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ This sample comes from the Angular documentation's "[Example Angular Internation
|
|||
2. `npm start` to see it run in English
|
||||
3. `npm run start:fr` to see it run with French translation.
|
||||
|
||||
>See the scripts in `package.json` for an explanation of these commands.
|
||||
> See the scripts in `package.json` for an explanation of these commands.
|
||||
|
||||
## Run in Stackblitz
|
||||
|
||||
|
|
|
|||
|
|
@ -8,26 +8,26 @@ You can choose to run multiple animations in parallel, or run discrete animation
|
|||
|
||||
The functions that control complex animation sequences are:
|
||||
|
||||
| Functions | Details |
|
||||
|:--- |:--- |
|
||||
| `query()` | Finds one or more inner HTML elements. |
|
||||
| Functions | Details |
|
||||
| :-------------------------------- | :------------------------------------------------------------- |
|
||||
| `query()` | Finds one or more inner HTML elements. |
|
||||
| `stagger()` | Applies a cascading delay to animations for multiple elements. |
|
||||
| [`group()`](api/animations/group) | Runs multiple animation steps in parallel. |
|
||||
| `sequence()` | Runs animation steps one after another. |
|
||||
| [`group()`](api/animations/group) | Runs multiple animation steps in parallel. |
|
||||
| `sequence()` | Runs animation steps one after another. |
|
||||
|
||||
## The query() function
|
||||
|
||||
Most complex animations rely on the `query()` function to find child elements and apply animations to them, basic examples of such are:
|
||||
|
||||
| Examples | Details |
|
||||
|:--- |:--- |
|
||||
| `query()` followed by `animate()` | Used to query simple HTML elements and directly apply animations to them. |
|
||||
| Examples | Details |
|
||||
| :------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `query()` followed by `animate()` | Used to query simple HTML elements and directly apply animations to them. |
|
||||
| `query()` followed by `animateChild()` | Used to query child elements, which themselves have animations metadata applied to them and trigger such animation \(which would be otherwise be blocked by the current/parent element's animation\). |
|
||||
|
||||
The first argument of `query()` is a [css selector](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) string which can also contain the following Angular-specific tokens:
|
||||
|
||||
| Tokens | Details |
|
||||
|:--- |:--- |
|
||||
| Tokens | Details |
|
||||
| :------------------------- | :------------------------------------------- |
|
||||
| `:enter` <br /> `:leave` | For entering/leaving elements. |
|
||||
| `:animating` | For elements currently animating. |
|
||||
| `@*` <br /> `@triggerName` | For elements with any—or a specific—trigger. |
|
||||
|
|
@ -47,12 +47,12 @@ After having queried child elements via `query()`, the `stagger()` function lets
|
|||
|
||||
The following example demonstrates how to use the `query()` and `stagger()` functions to animate a list \(of heroes\) adding each in sequence, with a slight delay, from top to bottom.
|
||||
|
||||
* Use `query()` to look for an element entering the page that meets certain criteria
|
||||
* For each of these elements, use `style()` to set the same initial style for the element.
|
||||
Make it transparent and use `transform` to move it out of position so that it can slide into place.
|
||||
- Use `query()` to look for an element entering the page that meets certain criteria
|
||||
- For each of these elements, use `style()` to set the same initial style for the element.
|
||||
Make it transparent and use `transform` to move it out of position so that it can slide into place.
|
||||
|
||||
* Use `stagger()` to delay each animation by 30 milliseconds
|
||||
* Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it
|
||||
- Use `stagger()` to delay each animation by 30 milliseconds
|
||||
- Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it
|
||||
|
||||
<docs-code header="src/app/hero-list-page.component.ts" path="adev/src/content/examples/animations/src/app/hero-list-page.component.ts" visibleRegion="page-animations"/>
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ But you might also want to configure animations that happen in parallel.
|
|||
For example, you might want to animate two CSS properties of the same element but use a different `easing` function for each one.
|
||||
For this, you can use the animation [`group()`](api/animations/group) function.
|
||||
|
||||
HELPFUL: The [`group()`](api/animations/group) function is used to group animation *steps*, rather than animated elements.
|
||||
HELPFUL: The [`group()`](api/animations/group) function is used to group animation _steps_, rather than animated elements.
|
||||
|
||||
The following example uses [`group()`](api/animations/group)s on both `:enter` and `:leave` for two different timing configurations, thus applying two independent animations to the same element in parallel.
|
||||
|
||||
|
|
@ -77,8 +77,8 @@ But what if you want to create an animation involving several animations happeni
|
|||
A second function called `sequence()` lets you run those same animations one after the other.
|
||||
Within `sequence()`, the animation steps consist of either `style()` or `animate()` function calls.
|
||||
|
||||
* Use `style()` to apply the provided styling data immediately.
|
||||
* Use `animate()` to apply styling data over a given time interval.
|
||||
- Use `style()` to apply the provided styling data immediately.
|
||||
- Use `animate()` to apply styling data over a given time interval.
|
||||
|
||||
## Filter animation example
|
||||
|
||||
|
|
@ -99,16 +99,16 @@ The `filterAnimation` in the component's decorator contains three transitions.
|
|||
|
||||
The code in this example performs the following tasks:
|
||||
|
||||
* Skips animations when the user first opens or navigates to this page \(the filter animation narrows what is already there, so it only works on elements that already exist in the DOM\)
|
||||
* Filters heroes based on the search input's value
|
||||
- Skips animations when the user first opens or navigates to this page \(the filter animation narrows what is already there, so it only works on elements that already exist in the DOM\)
|
||||
- Filters heroes based on the search input's value
|
||||
|
||||
For each change:
|
||||
|
||||
* Hides an element leaving the DOM by setting its opacity and width to 0
|
||||
* Animates an element entering the DOM over 300 milliseconds.
|
||||
During the animation, the element assumes its default width and opacity.
|
||||
- Hides an element leaving the DOM by setting its opacity and width to 0
|
||||
- Animates an element entering the DOM over 300 milliseconds.
|
||||
During the animation, the element assumes its default width and opacity.
|
||||
|
||||
* If there are multiple elements entering or leaving the DOM, staggers each animation starting at the top of the page, with a 50-millisecond delay between each element
|
||||
- If there are multiple elements entering or leaving the DOM, staggers each animation starting at the top of the page, with a 50-millisecond delay between each element
|
||||
|
||||
## Animating the items of a reordering list
|
||||
|
||||
|
|
@ -142,4 +142,4 @@ You might also be interested in the following:
|
|||
<docs-pill href="guide/legacy-animations/reusable-animations" title="Reusable animations"/>
|
||||
<docs-pill href="guide/routing/route-transition-animations" title="Route transition animations"/>
|
||||
<docs-pill href="guide/animations/migration" title="Migrating to Native CSS Animations"/>
|
||||
</docs-pill-row>
|
||||
</docs-pill-row>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ If you've never written any native CSS animations, there are a number of excelle
|
|||
[MDN's CSS Animations guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations)
|
||||
[W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp)
|
||||
[The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/)
|
||||
[CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners)
|
||||
[CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners)
|
||||
|
||||
and a couple of videos:
|
||||
[Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw)
|
||||
|
|
@ -128,12 +128,12 @@ If you have actions you would like to execute at certain points during animation
|
|||
[`OnAnimationStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationstart_event)
|
||||
[`OnAnimationEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event)
|
||||
[`OnAnimationIteration`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationitration_event)
|
||||
[`OnAnimationCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationcancel_event)
|
||||
[`OnAnimationCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationcancel_event)
|
||||
|
||||
[`OnTransitionStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionstart_event)
|
||||
[`OnTransitionRun`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionrun_event)
|
||||
[`OnTransitionEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionend_event)
|
||||
[`OnTransitionCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event)
|
||||
[`OnTransitionCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event)
|
||||
|
||||
The Web Animations API has a lot of additional functionality. [Take a look at the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) to see all the available animation APIs.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
Well-designed animations can make your application more fun and straightforward to use, but they aren't just cosmetic.
|
||||
Animations can improve your application and user experience in a number of ways:
|
||||
|
||||
* Without animations, web page transitions can seem abrupt and jarring
|
||||
* Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions
|
||||
* Good animations can smoothly direct the user's attention throughout a workflow
|
||||
- Without animations, web page transitions can seem abrupt and jarring
|
||||
- Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions
|
||||
- Good animations can smoothly direct the user's attention throughout a workflow
|
||||
|
||||
Angular provides `animate.enter` and `animate.leave` to animate your application's elements. These two features apply enter and leave CSS classes at the appropriate times or call functions to apply animations from third party libraries. `animate.enter` and `animate.leave` are not directives. They are special API supported directly by the Angular compiler. They can be used on elements directly and can also be used as a host binding.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Migrating away from Angular's Animations package
|
||||
|
||||
The `@angular/animations` package is deprecated as of v20.2, which also introduced the new `animate.enter` and `animate.leave` feature to add animations to your application. Using these new features, you can replace all animations based on `@angular/animations` with plain CSS or JS animation libraries. Removing `@angular/animations` from your application can significantly reduce the size of your JavaScript bundle. Native CSS animations generally offer superior performance, as they can benefit from hardware acceleration. This guide walks through the process of refactoring your code from `@angular/animations` to native CSS animations.
|
||||
The `@angular/animations` package is deprecated as of v20.2, which also introduced the new `animate.enter` and `animate.leave` feature to add animations to your application. Using these new features, you can replace all animations based on `@angular/animations` with plain CSS or JS animation libraries. Removing `@angular/animations` from your application can significantly reduce the size of your JavaScript bundle. Native CSS animations generally offer superior performance, as they can benefit from hardware acceleration. This guide walks through the process of refactoring your code from `@angular/animations` to native CSS animations.
|
||||
|
||||
## How to write animations in native CSS
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ If you've never written any native CSS animations, there are a number of excelle
|
|||
[MDN's CSS Animations guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations)
|
||||
[W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp)
|
||||
[The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/)
|
||||
[CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners)
|
||||
[CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners)
|
||||
|
||||
and a couple of videos:
|
||||
[Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw)
|
||||
|
|
@ -21,9 +21,11 @@ Check some of these various guides and tutorials out, and then come back to this
|
|||
Just like with the animations package, you can create reusable animations that can be shared across your application. The animations package version of this had you using the `animation()` function in a shared typescript file. The native CSS version of this is similar, but lives in a shared CSS file.
|
||||
|
||||
#### With Animations Package
|
||||
|
||||
<docs-code header="src/app/animations.ts" path="adev/src/content/examples/animations/src/app/animations.1.ts" visibleRegion="animation-example"/>
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="animation-shared"/>
|
||||
|
||||
Adding the class `animated-class` to an element would trigger the animation on that element.
|
||||
|
|
@ -35,11 +37,13 @@ Adding the class `animated-class` to an element would trigger the animation on t
|
|||
The animations package allowed you to define various states using the [`state()`](api/animations/state) function within a component. Examples might be an `open` or `closed` state containing the styles for each respective state within the definition. For example:
|
||||
|
||||
#### With Animations Package
|
||||
|
||||
<docs-code header="src/app/open-close.component.ts" path="adev/src/content/examples/animations/src/app/open-close.component.ts" visibleRegion="state1"/>
|
||||
|
||||
This same behavior can be accomplished natively by using CSS classes either using a keyframe animation or transition styling.
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code header="src/app/animations.css" path="adev/src/content/examples/animations/src/app/animations.css" visibleRegion="animation-states"/>
|
||||
|
||||
Triggering the `open` or `closed` state is done by toggling classes on the element in your component. You can find examples of how to do this in our [template guide](guide/templates/binding#css-class-and-style-property-bindings).
|
||||
|
|
@ -63,6 +67,7 @@ Similarly, you can use `transition-duration`, `transition-delay`, and `transitio
|
|||
The animations package required specifying triggers using the `trigger()` function and nesting all of your states within it. With native CSS, this is unnecessary. Animations can be triggered by toggling CSS styles or classes. Once a class is present on an element, the animation will occur. Removing the class will revert the element back to whatever CSS is defined for that element. This results in significantly less code to do the same animation. Here's an example:
|
||||
|
||||
#### With Animations Package
|
||||
|
||||
<docs-code-multifile>
|
||||
<docs-code header="src/app/open-close.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/open-close.component.ts" />
|
||||
<docs-code header="src/app/open-close.component.html" path="adev/src/content/examples/animations/src/app/animations-package/open-close.component.html" />
|
||||
|
|
@ -70,6 +75,7 @@ The animations package required specifying triggers using the `trigger()` functi
|
|||
</docs-code-multifile>
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/open-close.component.ts">
|
||||
<docs-code header="src/app/open-close.component.ts" path="adev/src/content/examples/animations/src/app/native-css/open-close.component.ts" />
|
||||
<docs-code header="src/app/open-close.component.html" path="adev/src/content/examples/animations/src/app/native-css/open-close.component.html" />
|
||||
|
|
@ -89,6 +95,7 @@ These state matching patterns are not needed at all when animating with CSS dire
|
|||
The animations package offers the ability to animate things that have been historically difficult to animate, like animating a set height to `height: auto`. You can now do this with pure CSS as well.
|
||||
|
||||
#### With Animations Package
|
||||
|
||||
<docs-code-multifile>
|
||||
<docs-code header="src/app/auto-height.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/auto-height.component.ts" />
|
||||
<docs-code header="src/app/auto-height.component.html" path="adev/src/content/examples/animations/src/app/animations-package/auto-height.component.html" />
|
||||
|
|
@ -98,6 +105,7 @@ The animations package offers the ability to animate things that have been histo
|
|||
You can use css-grid to animate to auto height.
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.ts">
|
||||
<docs-code header="src/app/auto-height.component.ts" path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.ts" />
|
||||
<docs-code header="src/app/auto-height.component.html" path="adev/src/content/examples/animations/src/app/native-css/auto-height.component.html" />
|
||||
|
|
@ -111,6 +119,7 @@ If you don't have to worry about supporting all browsers, you can also check out
|
|||
The animations package offered the previously mentioned pattern matching for entering and leaving but also included the shorthand aliases of `:enter` and `:leave`.
|
||||
|
||||
#### With Animations Package
|
||||
|
||||
<docs-code-multifile>
|
||||
<docs-code header="src/app/insert-remove.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/insert-remove.component.ts" />
|
||||
<docs-code header="src/app/insert-remove.component.html" path="adev/src/content/examples/animations/src/app/animations-package/insert-remove.component.html" />
|
||||
|
|
@ -120,6 +129,7 @@ The animations package offered the previously mentioned pattern matching for ent
|
|||
Here's how the same thing can be accomplished without the animations package using `animate.enter`.
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/insert.component.ts">
|
||||
<docs-code header="src/app/insert.component.ts" path="adev/src/content/examples/animations/src/app/native-css/insert.component.ts" />
|
||||
<docs-code header="src/app/insert.component.html" path="adev/src/content/examples/animations/src/app/native-css/insert.component.html" />
|
||||
|
|
@ -129,6 +139,7 @@ Here's how the same thing can be accomplished without the animations package usi
|
|||
Use `animate.leave` to animate elements as they leave the view, which will apply the specified CSS classes to the element as it leaves the view.
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/remove.component.ts">
|
||||
<docs-code header="src/app/remove.component.ts" path="adev/src/content/examples/animations/src/app/native-css/remove.component.ts" />
|
||||
<docs-code header="src/app/remove.component.html" path="adev/src/content/examples/animations/src/app/native-css/remove.component.html" />
|
||||
|
|
@ -142,6 +153,7 @@ For more information on `animate.enter` and `animate.leave`, see the [Enter and
|
|||
Along with the aforementioned `:enter` and `:leave`, there's also `:increment` and `:decrement`. You can animate these also by adding and removing classes. Unlike the animation package built-in aliases, there is no automatic application of classes when the values go up or down. You can apply the appropriate classes programmatically. Here's an example:
|
||||
|
||||
#### With Animations Package
|
||||
|
||||
<docs-code-multifile>
|
||||
<docs-code header="src/app/increment-decrement.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/increment-decrement.component.ts" />
|
||||
<docs-code header="src/app/increment-decrement.component.html" path="adev/src/content/examples/animations/src/app/animations-package/increment-decrement.component.html" />
|
||||
|
|
@ -149,6 +161,7 @@ Along with the aforementioned `:enter` and `:leave`, there's also `:increment` a
|
|||
</docs-code-multifile>
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.ts">
|
||||
<docs-code header="src/app/increment-decrement.component.ts" path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.ts" />
|
||||
<docs-code header="src/app/increment-decrement.component.html" path="adev/src/content/examples/animations/src/app/native-css/increment-decrement.component.html" />
|
||||
|
|
@ -185,12 +198,12 @@ The animations package exposed callbacks for you to use in the case that you wan
|
|||
[`OnAnimationStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationstart_event)
|
||||
[`OnAnimationEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event)
|
||||
[`OnAnimationIteration`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationitration_event)
|
||||
[`OnAnimationCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationcancel_event)
|
||||
[`OnAnimationCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationcancel_event)
|
||||
|
||||
[`OnTransitionStart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionstart_event)
|
||||
[`OnTransitionRun`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionrun_event)
|
||||
[`OnTransitionEnd`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitionend_event)
|
||||
[`OnTransitionCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event)
|
||||
[`OnTransitionCancel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/transitioncancel_event)
|
||||
|
||||
The Web Animations API has a lot of additional functionality. [Take a look at the documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) to see all the available animation APIs.
|
||||
|
||||
|
|
@ -211,6 +224,7 @@ To toggle classes for child nodes within a template, you can use class and style
|
|||
The `stagger()` function allowed you to delay the animation of each item in a list of items by a specified time to create a cascade effect. You can replicate this behavior in native CSS by utilizing `animation-delay` or `transition-delay`. Here is an example of what that CSS might look like.
|
||||
|
||||
#### With Animations Package
|
||||
|
||||
<docs-code-multifile>
|
||||
<docs-code header="src/app/stagger.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/stagger.component.ts" />
|
||||
<docs-code header="src/app/stagger.component.html" path="adev/src/content/examples/animations/src/app/animations-package/stagger.component.html" />
|
||||
|
|
@ -218,6 +232,7 @@ The `stagger()` function allowed you to delay the animation of each item in a li
|
|||
</docs-code-multifile>
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/stagger.component.ts">
|
||||
<docs-code header="src/app/stagger.component.ts" path="adev/src/content/examples/animations/src/app/native-css/stagger.component.ts" />
|
||||
<docs-code header="src/app/stagger.component.html" path="adev/src/content/examples/animations/src/app/native-css/stagger.component.html" />
|
||||
|
|
@ -241,6 +256,7 @@ In this example, the `rotate` and `fade-in` animations fire at the same time.
|
|||
Items reordering in a list works out of the box using the previously described techniques. No additional special work is required. Items in a `@for` loop will be removed and re-added properly, which will fire off animations using `@starting-styles` for entry animations. Alternatively, you can use `animate.enter` for this same behavior. Use `animate.leave` to animate elements as they are removed, as seen in the example above.
|
||||
|
||||
#### With Animations Package<
|
||||
|
||||
<docs-code-multifile>
|
||||
<docs-code header="src/app/reorder.component.ts" path="adev/src/content/examples/animations/src/app/animations-package/reorder.component.ts" />
|
||||
<docs-code header="src/app/reorder.component.html" path="adev/src/content/examples/animations/src/app/animations-package/reorder.component.html" />
|
||||
|
|
@ -248,13 +264,13 @@ Items reordering in a list works out of the box using the previously described t
|
|||
</docs-code-multifile>
|
||||
|
||||
#### With Native CSS
|
||||
|
||||
<docs-code-multifile preview path="adev/src/content/examples/animations/src/app/native-css/reorder.component.ts">
|
||||
<docs-code header="src/app/reorder.component.ts" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.ts" />
|
||||
<docs-code header="src/app/reorder.component.html" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.html" />
|
||||
<docs-code header="src/app/reorder.component.css" path="adev/src/content/examples/animations/src/app/native-css/reorder.component.css" />
|
||||
</docs-code-multifile>
|
||||
|
||||
|
||||
## Migrating usages of AnimationPlayer
|
||||
|
||||
The `AnimationPlayer` class allows access to an animation to do more advanced things like pause, play, restart, and finish an animation through code. All of these things can be handled natively as well.
|
||||
|
|
@ -263,4 +279,4 @@ You can retrieve animations off an element directly using [`Element.getAnimation
|
|||
|
||||
## Route Transitions
|
||||
|
||||
You can use view transitions to animate between routes. See the [Route Transition Animations Guide](guide/routing/route-transition-animations) to get started.
|
||||
You can use view transitions to animate between routes. See the [Route Transition Animations Guide](guide/routing/route-transition-animations) to get started.
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ Animation provides the illusion of motion: HTML elements change styling over tim
|
|||
Well-designed animations can make your application more fun and straightforward to use, but they aren't just cosmetic.
|
||||
Animations can improve your application and user experience in a number of ways:
|
||||
|
||||
* Without animations, web page transitions can seem abrupt and jarring
|
||||
* Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions
|
||||
* Good animations intuitively call the user's attention to where it is needed
|
||||
- Without animations, web page transitions can seem abrupt and jarring
|
||||
- Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions
|
||||
- Good animations intuitively call the user's attention to where it is needed
|
||||
|
||||
Typically, animations involve multiple style *transformations* over time.
|
||||
Typically, animations involve multiple style _transformations_ over time.
|
||||
An HTML element can move, change color, grow or shrink, fade, or slide off the page.
|
||||
These changes can occur simultaneously or sequentially. You can control the timing of each transformation.
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ This function takes two arguments:
|
|||
A unique name like `open` or `closed` and a `style()` function.
|
||||
|
||||
Use the `style()` function to define a set of styles to associate with a given state name.
|
||||
You must use *camelCase* for style attributes that contain dashes, such as `backgroundColor` or wrap them in quotes, such as `'background-color'`.
|
||||
You must use _camelCase_ for style attributes that contain dashes, such as `backgroundColor` or wrap them in quotes, such as `'background-color'`.
|
||||
|
||||
Let's see how Angular's [`state()`](api/animations/state) function works with the `style()` function to set CSS style attributes.
|
||||
In this code snippet, multiple style attributes are set at the same time for the state.
|
||||
|
|
@ -114,7 +114,7 @@ In the following `closed` state, the button has a height of 100 pixels, an opaci
|
|||
In Angular, you can set multiple styles without any animation.
|
||||
However, without further refinement, the button instantly transforms with no fade, no shrinkage, or other visible indicator that a change is occurring.
|
||||
|
||||
To make the change less abrupt, you need to define an animation *transition* to specify the changes that occur between one state and another over a period of time.
|
||||
To make the change less abrupt, you need to define an animation _transition_ to specify the changes that occur between one state and another over a period of time.
|
||||
The `transition()` function accepts two arguments:
|
||||
The first argument accepts an expression that defines the direction between two transition states, and the second argument accepts one or a series of `animate()` steps.
|
||||
|
||||
|
|
@ -146,34 +146,34 @@ The first part, `duration`, is required.
|
|||
The duration can be expressed in milliseconds as a number without quotes, or in seconds with quotes and a time specifier.
|
||||
For example, a duration of a tenth of a second can be expressed as follows:
|
||||
|
||||
* As a plain number, in milliseconds:
|
||||
`100`
|
||||
- As a plain number, in milliseconds:
|
||||
`100`
|
||||
|
||||
* In a string, as milliseconds:
|
||||
`'100ms'`
|
||||
- In a string, as milliseconds:
|
||||
`'100ms'`
|
||||
|
||||
* In a string, as seconds:
|
||||
`'0.1s'`
|
||||
- In a string, as seconds:
|
||||
`'0.1s'`
|
||||
|
||||
The second argument, `delay`, has the same syntax as `duration`.
|
||||
For example:
|
||||
|
||||
* Wait for 100ms and then run for 200ms: `'0.2s 100ms'`
|
||||
- Wait for 100ms and then run for 200ms: `'0.2s 100ms'`
|
||||
|
||||
The third argument, `easing`, controls how the animation [accelerates and decelerates](https://easings.net) during its runtime.
|
||||
For example, `ease-in` causes the animation to begin slowly, and to pick up speed as it progresses.
|
||||
|
||||
* Wait for 100ms, run for 200ms.
|
||||
Use a deceleration curve to start out fast and slowly decelerate to a resting point:
|
||||
`'0.2s 100ms ease-out'`
|
||||
- Wait for 100ms, run for 200ms.
|
||||
Use a deceleration curve to start out fast and slowly decelerate to a resting point:
|
||||
`'0.2s 100ms ease-out'`
|
||||
|
||||
* Run for 200ms, with no delay.
|
||||
Use a standard curve to start slow, accelerate in the middle, and then decelerate slowly at the end:
|
||||
`'0.2s ease-in-out'`
|
||||
- Run for 200ms, with no delay.
|
||||
Use a standard curve to start slow, accelerate in the middle, and then decelerate slowly at the end:
|
||||
`'0.2s ease-in-out'`
|
||||
|
||||
* Start immediately, run for 200ms.
|
||||
Use an acceleration curve to start slow and end at full velocity:
|
||||
`'0.2s ease-in'`
|
||||
- Start immediately, run for 200ms.
|
||||
Use an acceleration curve to start slow and end at full velocity:
|
||||
`'0.2s ease-in'`
|
||||
|
||||
HELPFUL: See the Material Design website's topic on [Natural easing curves](https://material.io/design/motion/speed.html#easing) for general information on easing curves.
|
||||
|
||||
|
|
@ -191,20 +191,20 @@ This example adds a state transition from the `closed` state to the `open` state
|
|||
|
||||
HELPFUL: Some additional notes on using styles within [`state`](api/animations/state) and `transition` functions.
|
||||
|
||||
* Use [`state()`](api/animations/state) to define styles that are applied at the end of each transition, they persist after the animation completes
|
||||
* Use `transition()` to define intermediate styles, which create the illusion of motion during the animation
|
||||
* When animations are disabled, `transition()` styles can be skipped, but [`state()`](api/animations/state) styles can't
|
||||
* Include multiple state pairs within the same `transition()` argument:
|
||||
- Use [`state()`](api/animations/state) to define styles that are applied at the end of each transition, they persist after the animation completes
|
||||
- Use `transition()` to define intermediate styles, which create the illusion of motion during the animation
|
||||
- When animations are disabled, `transition()` styles can be skipped, but [`state()`](api/animations/state) styles can't
|
||||
- Include multiple state pairs within the same `transition()` argument:
|
||||
|
||||
<docs-code language="typescript">
|
||||
|
||||
transition( 'on => off, off => void' )
|
||||
transition( 'on => off, off => void' )
|
||||
|
||||
</docs-code>
|
||||
|
||||
### Triggering the animation
|
||||
|
||||
An animation requires a *trigger*, so that it knows when to start.
|
||||
An animation requires a _trigger_, so that it knows when to start.
|
||||
The `trigger()` function collects the states and transitions, and gives the animation a name, so that you can attach it to the triggering element in the HTML template.
|
||||
|
||||
The `trigger()` function describes the property name to watch for changes.
|
||||
|
|
@ -271,14 +271,14 @@ The functional API provided by the `@angular/animations` module provides a domai
|
|||
See the [API reference](api#animations) for a complete listing and syntax details of the core functions and related data structures.
|
||||
|
||||
| Function name | What it does |
|
||||
|:--- |:--- |
|
||||
| :-------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `trigger()` | Kicks off the animation and serves as a container for all other animation function calls. HTML template binds to `triggerName`. Use the first argument to declare a unique trigger name. Uses array syntax. |
|
||||
| `style()` | Defines one or more CSS styles to use in animations. Controls the visual appearance of HTML elements during animations. Uses object syntax. |
|
||||
| [`state()`](api/animations/state) | Creates a named set of CSS styles that should be applied on successful transition to a given state. The state can then be referenced by name within other animation functions. |
|
||||
| `animate()` | Specifies the timing information for a transition. Optional values for `delay` and `easing`. Can contain `style()` calls within. |
|
||||
| `transition()` | Defines the animation sequence between two named states. Uses array syntax. |
|
||||
| `keyframes()` | Allows a sequential change between styles within a specified time interval. Use within `animate()`. Can include multiple `style()` calls within each `keyframe()`. Uses array syntax. |
|
||||
| [`group()`](api/animations/group) | Specifies a group of animation steps \(*inner animations*\) to be run in parallel. Animation continues only after all inner animation steps have completed. Used within `sequence()` or `transition()`. |
|
||||
| [`group()`](api/animations/group) | Specifies a group of animation steps \(_inner animations_\) to be run in parallel. Animation continues only after all inner animation steps have completed. Used within `sequence()` or `transition()`. |
|
||||
| `query()` | Finds one or more inner HTML elements within the current element. |
|
||||
| `sequence()` | Specifies a list of animation steps that are run sequentially, one by one. |
|
||||
| `stagger()` | Staggers the starting time for animations for multiple elements. |
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ In Angular, transition states can be defined explicitly through the [`state()`](
|
|||
|
||||
### Wildcard state
|
||||
|
||||
An asterisk `*` or *wildcard* matches any animation state.
|
||||
An asterisk `*` or _wildcard_ matches any animation state.
|
||||
This is useful for defining transitions that apply regardless of the HTML element's start or end state.
|
||||
|
||||
For example, a transition of `open => *` applies when the element's state changes from open to anything else.
|
||||
|
|
@ -45,7 +45,7 @@ Transitions are matched in the order in which they are defined.
|
|||
Thus, you can apply other transitions on top of the `* => *` transition.
|
||||
For example, define style changes or animations that would apply just to `open => closed`, then use `* => *` as a fallback for state pairings that aren't otherwise called out.
|
||||
|
||||
To do this, list the more specific transitions *before* `* => *`.
|
||||
To do this, list the more specific transitions _before_ `* => *`.
|
||||
|
||||
### Use wildcards with styles
|
||||
|
||||
|
|
@ -63,9 +63,9 @@ See [Animating entering and leaving a view](guide/legacy-animations/transition-a
|
|||
|
||||
Combine wildcard and void states in a transition to trigger animations that enter and leave the page:
|
||||
|
||||
* A transition of `* => void` applies when the element leaves a view, regardless of what state it was in before it left
|
||||
* A transition of `void => *` applies when the element enters a view, regardless of what state it assumes when entering
|
||||
* The wildcard state `*` matches to *any* state, including `void`
|
||||
- A transition of `* => void` applies when the element leaves a view, regardless of what state it was in before it left
|
||||
- A transition of `void => *` applies when the element enters a view, regardless of what state it assumes when entering
|
||||
- The wildcard state `*` matches to _any_ state, including `void`
|
||||
|
||||
## Animate entering and leaving a view
|
||||
|
||||
|
|
@ -73,8 +73,8 @@ This section shows how to animate elements entering or leaving a page.
|
|||
|
||||
Add a new behavior:
|
||||
|
||||
* When you add a hero to the list of heroes, it appears to fly onto the page from the left
|
||||
* When you remove a hero from the list, it appears to fly out to the right
|
||||
- When you add a hero to the list of heroes, it appears to fly onto the page from the left
|
||||
- When you remove a hero from the list, it appears to fly out to the right
|
||||
|
||||
<docs-code header="src/app/hero-list-enter-leave.component.ts" path="adev/src/content/examples/animations/src/app/hero-list-enter-leave.component.ts" visibleRegion="animationdef"/>
|
||||
|
||||
|
|
@ -87,8 +87,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', [ … ] ); // alias for void => _
|
||||
transition ( ':leave', [ … ] ); // alias for _ => void
|
||||
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -167,11 +167,11 @@ You can't selectively turn off multiple animations on a single element.<!-- vale
|
|||
|
||||
A selective child animations can still be run on a disabled parent in one of the following ways:
|
||||
|
||||
* A parent animation can use the [`query()`](api/animations/query) function to collect inner elements located in disabled areas of the HTML template.
|
||||
Those elements can still animate.
|
||||
- A parent animation can use the [`query()`](api/animations/query) function to collect inner elements located in disabled areas of the HTML template.
|
||||
Those elements can still animate.
|
||||
<!-- vale on -->
|
||||
|
||||
* A child animation can be queried by a parent and then later animated with the `animateChild()` function
|
||||
- A child animation can be queried by a parent and then later animated with the `animateChild()` function
|
||||
|
||||
#### Disable all animations
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ HELPFUL: Disabling animations application-wide is useful during end-to-end \(E2E
|
|||
|
||||
## Animation callbacks
|
||||
|
||||
The animation `trigger()` function emits *callbacks* when it starts and when it finishes.
|
||||
The animation `trigger()` function emits _callbacks_ when it starts and when it finishes.
|
||||
The following example features a component that contains an `openClose` trigger.
|
||||
|
||||
<docs-code header="src/app/open-close.component.ts" path="adev/src/content/examples/animations/src/app/open-close.component.ts" visibleRegion="events1"/>
|
||||
|
|
@ -199,7 +199,7 @@ For example, an **InProgress** button can be set up to have its own looping anim
|
|||
Another animation can be called when the current animation finishes.
|
||||
For example, the button goes from the `inProgress` state to the `closed` state when the API call is completed.
|
||||
|
||||
An animation can influence an end user to *perceive* the operation as faster, even when it is not.
|
||||
An animation can influence an end user to _perceive_ the operation as faster, even when it is not.
|
||||
|
||||
Callbacks can serve as a debugging tool, for example in conjunction with `console.warn()` to view the application's progress in a browser's Developer JavaScript Console.
|
||||
The following code snippet creates console log output for the original example, a button with the two states of `open` and `closed`.
|
||||
|
|
@ -208,7 +208,7 @@ The following code snippet creates console log output for the original example,
|
|||
|
||||
## Keyframes
|
||||
|
||||
To create an animation with multiple steps run in sequence, use *keyframes*.
|
||||
To create an animation with multiple steps run in sequence, use _keyframes_.
|
||||
|
||||
Angular's `keyframe()` function allows several style changes within a single timing segment.
|
||||
For example, the button, instead of fading, could change color several times over a single 2-second time span.
|
||||
|
|
@ -243,8 +243,8 @@ Use keyframes to create a pulse effect in your animations by defining styles at
|
|||
|
||||
Here's an example of using keyframes to create a pulse effect:
|
||||
|
||||
* The original `open` and `closed` states, with the original changes in height, color, and opacity, occurring over a timeframe of 1 second
|
||||
* A keyframes sequence inserted in the middle that causes the button to appear to pulsate irregularly over the course of that same 1 second timeframe
|
||||
- The original `open` and `closed` states, with the original changes in height, color, and opacity, occurring over a timeframe of 1 second
|
||||
- A keyframes sequence inserted in the middle that causes the button to appear to pulsate irregularly over the course of that same 1 second timeframe
|
||||
|
||||
<img alt="keyframes with irregular pulsation" src="assets/images/guide/animations/keyframes-pulsation.png">
|
||||
|
||||
|
|
@ -260,14 +260,14 @@ The W3C maintains a list of animatable properties on its [CSS Transitions](https
|
|||
|
||||
For properties with a numeric value, define a unit by providing the value as a string, in quotes, with the appropriate suffix:
|
||||
|
||||
* 50 pixels:
|
||||
`'50px'`
|
||||
- 50 pixels:
|
||||
`'50px'`
|
||||
|
||||
* Relative font size:
|
||||
`'3em'`
|
||||
- Relative font size:
|
||||
`'3em'`
|
||||
|
||||
* Percentage:
|
||||
`'100%'`
|
||||
- Percentage:
|
||||
`'100%'`
|
||||
|
||||
You can also provide the value as a number. In such cases Angular assumes a default unit of pixels, or `px`.
|
||||
Expressing 50 pixels as `50` is the same as saying `'50px'`.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Accordion API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/accordion/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Combobox API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/combobox/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Grid API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/grid/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Listbox API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/listbox/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Menu API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/menu/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Radio Group API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/radio/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Tabs API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/tabs/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Toolbar API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/toolbar/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- TODO: Add a link to the Tree API reference. -->
|
||||
</docs-pill-row>
|
||||
|
||||
|
||||
<!-- TODO: Add a top level component preview with code example hidden.
|
||||
|
||||
<docs-code-multifile preview themed hideCode path="adev/src/content/examples/aria/src/tree/app/app.component.ts">
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ TIP: This guide assumes you've already read the [Essentials Guide](essentials).
|
|||
|
||||
Every component must have:
|
||||
|
||||
* A TypeScript class with _behaviors_ such as handling user input and fetching data from a server
|
||||
* An HTML template that controls what renders into the DOM
|
||||
* A [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML
|
||||
- A TypeScript class with _behaviors_ such as handling user input and fetching data from a server
|
||||
- An HTML template that controls what renders into the DOM
|
||||
- A [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML
|
||||
|
||||
You provide Angular-specific information for a component by adding a `@Component` [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) on top of the TypeScript class:
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ import {ProfilePhoto} from './profile-photo';
|
|||
export class UserProfile { }
|
||||
```
|
||||
|
||||
By default, Angular components are *standalone*, meaning that you can directly add them to the `imports` array of other components. Components created with an earlier version of Angular may instead specify `standalone: false` in their `@Component` decorator. For these components, you instead import the `NgModule` in which the component is defined. See the full [`NgModule` guide](guide/ngmodules) for details.
|
||||
By default, Angular components are _standalone_, meaning that you can directly add them to the `imports` array of other components. Components created with an earlier version of Angular may instead specify `standalone: false` in their `@Component` decorator. For these components, you instead import the `NgModule` in which the component is defined. See the full [`NgModule` guide](guide/ngmodules) for details.
|
||||
|
||||
Important: In Angular versions before 19.0.0, the `standalone` option defaults to `false`.
|
||||
|
||||
|
|
@ -97,8 +97,8 @@ You show a component by creating a matching HTML element in the template of _oth
|
|||
export class ProfilePhoto { }
|
||||
|
||||
@Component({
|
||||
imports: [ProfilePhoto],
|
||||
template: `<profile-photo />`
|
||||
imports: [ProfilePhoto],
|
||||
template: `<profile-photo />`
|
||||
})
|
||||
export class UserProfile { }
|
||||
</docs-code>
|
||||
|
|
@ -121,5 +121,4 @@ flowchart TD
|
|||
E[UserBio]
|
||||
```
|
||||
|
||||
|
||||
This tree structure is important to understanding several other Angular concepts, including [dependency injection](guide/di) and [child queries](guide/components/queries).
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ elements that don't match one of the component's placeholders do not render into
|
|||
|
||||
## Fallback content
|
||||
|
||||
Angular can show *fallback content* for a component's `<ng-content>` placeholder if that component doesn't have any matching child content. You can specify fallback content by adding child content to the `<ng-content>` element itself.
|
||||
Angular can show _fallback content_ for a component's `<ng-content>` placeholder if that component doesn't have any matching child content. You can specify fallback content by adding child content to the `<ng-content>` element itself.
|
||||
|
||||
```angular-html
|
||||
<!-- Component template -->
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ When you use a component, you commonly want to pass some data to it. A component
|
|||
<docs-code language="ts" highlight="[5]">
|
||||
import {Component, input} from '@angular/core';
|
||||
|
||||
@Component({/*...*/})
|
||||
@Component({/_..._/})
|
||||
export class CustomSlider {
|
||||
// Declare an input named 'value' with a default value of zero.
|
||||
value = input(0);
|
||||
// Declare an input named 'value' with a default value of zero.
|
||||
value = input(0);
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -60,13 +60,13 @@ The `input` function returns an `InputSignal`. You can read the value by calling
|
|||
<docs-code language="ts" highlight="[5]">
|
||||
import {Component, input} from '@angular/core';
|
||||
|
||||
@Component({/*...*/})
|
||||
@Component({/_..._/})
|
||||
export class CustomSlider {
|
||||
// Declare an input named 'value' with a default value of zero.
|
||||
value = input(0);
|
||||
// Declare an input named 'value' with a default value of zero.
|
||||
value = input(0);
|
||||
|
||||
// Create a computed expression that reads the value input
|
||||
label = computed(() => `The slider's value is ${this.value()}`);
|
||||
// Create a computed expression that reads the value input
|
||||
label = computed(() => `The slider's value is ${this.value()}`);
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ export class CustomSlider {
|
|||
}
|
||||
|
||||
function trimString(value: string | undefined): string {
|
||||
return value?.trim() ?? '';
|
||||
return value?.trim() ?? '';
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ export class CustomSlider {
|
|||
}
|
||||
|
||||
function appendPx(value: number): string {
|
||||
return `${value}px`;
|
||||
return `${value}px`;
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -146,10 +146,10 @@ Angular includes two built-in transform functions for the two most common scenar
|
|||
<docs-code language="ts">
|
||||
import {Component, input, booleanAttribute, numberAttribute} from '@angular/core';
|
||||
|
||||
@Component({/*...*/})
|
||||
@Component({/_..._/})
|
||||
export class CustomSlider {
|
||||
disabled = input(false, {transform: booleanAttribute});
|
||||
value = input(0, {transform: numberAttribute});
|
||||
disabled = input(false, {transform: booleanAttribute});
|
||||
value = input(0, {transform: numberAttribute});
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ export class CustomCardHeader {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-card',
|
||||
template: '<custom-card-header>Visit sunny California!</custom-card-header>',
|
||||
selector: 'custom-card',
|
||||
template: '<custom-card-header>Visit sunny California!</custom-card-header>',
|
||||
})
|
||||
export class CustomCard {
|
||||
header = viewChild(CustomCardHeader);
|
||||
headerText = computed(() => this.header()?.text);
|
||||
header = viewChild(CustomCardHeader);
|
||||
headerText = computed(() => this.header()?.text);
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -50,15 +50,14 @@ export class CustomCardAction {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-card',
|
||||
template: `
|
||||
<custom-card-action>Save</custom-card-action>
|
||||
selector: 'custom-card',
|
||||
template: ` <custom-card-action>Save</custom-card-action>
|
||||
<custom-card-action>Cancel</custom-card-action>
|
||||
`,
|
||||
})
|
||||
export class CustomCard {
|
||||
actions = viewChildren(CustomCardAction);
|
||||
actionsTexts = computed(() => this.actions().map(action => action.text);
|
||||
actions = viewChildren(CustomCardAction);
|
||||
actionsTexts = computed(() => this.actions().map(action => action.text);
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -80,19 +79,18 @@ export class CustomToggle {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-expando',
|
||||
/*...*/
|
||||
selector: 'custom-expando',
|
||||
/_..._/
|
||||
})
|
||||
export class CustomExpando {
|
||||
toggle = contentChild(CustomToggle);
|
||||
toggleText = computed(() => this.toggle()?.text);
|
||||
toggle = contentChild(CustomToggle);
|
||||
toggleText = computed(() => this.toggle()?.text);
|
||||
}
|
||||
|
||||
@Component({
|
||||
/* ... */
|
||||
// CustomToggle is used inside CustomExpando as content.
|
||||
template: `
|
||||
<custom-expando>
|
||||
@Component({
|
||||
/_ ... _/
|
||||
// CustomToggle is used inside CustomExpando as content.
|
||||
template: ` <custom-expando>
|
||||
<custom-toggle>Show</custom-toggle>
|
||||
</custom-expando>
|
||||
`
|
||||
|
|
@ -118,18 +116,17 @@ export class CustomMenuItem {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-menu',
|
||||
/*...*/
|
||||
selector: 'custom-menu',
|
||||
/_..._/
|
||||
})
|
||||
export class CustomMenu {
|
||||
items = contentChildren(CustomMenuItem);
|
||||
itemTexts = computed(() => this.items().map(item => item.text));
|
||||
items = contentChildren(CustomMenuItem);
|
||||
itemTexts = computed(() => this.items().map(item => item.text));
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'user-profile',
|
||||
template: `
|
||||
<custom-menu>
|
||||
selector: 'user-profile',
|
||||
template: ` <custom-menu>
|
||||
<custom-menu-item>Cheese</custom-menu-item>
|
||||
<custom-menu-item>Tomato</custom-menu-item>
|
||||
</custom-menu>
|
||||
|
|
@ -146,7 +143,7 @@ export class UserProfile { }
|
|||
|
||||
If a child query (`viewChild` or `contentChild`) does not find a result, its value is `undefined`. This may occur if the target element is hidden by a control flow statement like `@if` or `@for`. Because of this, the child queries return a signal that include `undefined` in their value type.
|
||||
|
||||
In some cases, especially with `viewChild`, you know with certainty that a specific child is always available. In other cases, you may want to strictly enforce that a specific child is present. For these cases, you can use a *required query*.
|
||||
In some cases, especially with `viewChild`, you know with certainty that a specific child is always available. In other cases, you may want to strictly enforce that a specific child is present. For these cases, you can use a _required query_.
|
||||
|
||||
```angular-ts
|
||||
@Component({/* ... */})
|
||||
|
|
@ -230,7 +227,7 @@ Developers most commonly use `read` to retrieve `ElementRef` and `TemplateRef`.
|
|||
### Content descendants
|
||||
|
||||
By default, `contentChildren` queries find only _direct_ children of the component and do not traverse into descendants.
|
||||
`contentChild` queries do traverse into descendants by default.
|
||||
`contentChild` queries do traverse into descendants by default.
|
||||
|
||||
<docs-code language="angular-ts" highlight="[13, 14, 15, 16]">
|
||||
@Component({
|
||||
|
|
@ -243,9 +240,8 @@ export class CustomExpando {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'user-profile',
|
||||
template: `
|
||||
<custom-expando>
|
||||
selector: 'user-profile',
|
||||
template: ` <custom-expando>
|
||||
<some-other-component>
|
||||
<custom-toggle>Show</custom-toggle>
|
||||
</some-other-component>
|
||||
|
|
@ -260,6 +256,7 @@ In the example above, `CustomExpando` cannot find `<custom-toggle>` with `conten
|
|||
View queries do not have this option because they _always_ traverse into descendants.
|
||||
|
||||
## Decorator-based queries
|
||||
|
||||
TIP: While the Angular team recommends using the signal-based query function for new projects, the
|
||||
original decorator-based query APIs remain fully supported.
|
||||
|
||||
|
|
@ -279,15 +276,15 @@ export class CustomCardHeader {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-card',
|
||||
template: '<custom-card-header>Visit sunny California!</custom-card-header>',
|
||||
selector: 'custom-card',
|
||||
template: '<custom-card-header>Visit sunny California!</custom-card-header>',
|
||||
})
|
||||
export class CustomCard {
|
||||
@ViewChild(CustomCardHeader) header: CustomCardHeader;
|
||||
@ViewChild(CustomCardHeader) header: CustomCardHeader;
|
||||
|
||||
ngAfterViewInit() {
|
||||
console.log(this.header.text);
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
console.log(this.header.text);
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -309,20 +306,19 @@ export class CustomCardAction {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-card',
|
||||
template: `
|
||||
<custom-card-action>Save</custom-card-action>
|
||||
selector: 'custom-card',
|
||||
template: ` <custom-card-action>Save</custom-card-action>
|
||||
<custom-card-action>Cancel</custom-card-action>
|
||||
`,
|
||||
})
|
||||
export class CustomCard {
|
||||
@ViewChildren(CustomCardAction) actions: QueryList<CustomCardAction>;
|
||||
@ViewChildren(CustomCardAction) actions: QueryList<CustomCardAction>;
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.actions.forEach(action => {
|
||||
console.log(action.text);
|
||||
});
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
this.actions.forEach(action => {
|
||||
console.log(action.text);
|
||||
});
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -342,21 +338,20 @@ export class CustomToggle {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-expando',
|
||||
/*...*/
|
||||
selector: 'custom-expando',
|
||||
/_..._/
|
||||
})
|
||||
export class CustomExpando {
|
||||
@ContentChild(CustomToggle) toggle: CustomToggle;
|
||||
@ContentChild(CustomToggle) toggle: CustomToggle;
|
||||
|
||||
ngAfterContentInit() {
|
||||
console.log(this.toggle.text);
|
||||
}
|
||||
ngAfterContentInit() {
|
||||
console.log(this.toggle.text);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'user-profile',
|
||||
template: `
|
||||
<custom-expando>
|
||||
selector: 'user-profile',
|
||||
template: ` <custom-expando>
|
||||
<custom-toggle>Show</custom-toggle>
|
||||
</custom-expando>
|
||||
`
|
||||
|
|
@ -382,23 +377,22 @@ export class CustomMenuItem {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'custom-menu',
|
||||
/*...*/
|
||||
selector: 'custom-menu',
|
||||
/_..._/
|
||||
})
|
||||
export class CustomMenu {
|
||||
@ContentChildren(CustomMenuItem) items: QueryList<CustomMenuItem>;
|
||||
@ContentChildren(CustomMenuItem) items: QueryList<CustomMenuItem>;
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.items.forEach(item => {
|
||||
console.log(item.text);
|
||||
});
|
||||
}
|
||||
ngAfterContentInit() {
|
||||
this.items.forEach(item => {
|
||||
console.log(item.text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'user-profile',
|
||||
template: `
|
||||
<custom-menu>
|
||||
selector: 'user-profile',
|
||||
template: ` <custom-menu>
|
||||
<custom-menu-item>Cheese</custom-menu-item>
|
||||
<custom-menu-item>Tomato</custom-menu-item>
|
||||
</custom-menu>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@ default, the Angular CLI uses `app-`.
|
|||
|
||||
IMPORTANT: Angular uses the `ng` selector prefix for its own framework APIs. Never use `ng` as a selector prefix for your own custom components.
|
||||
|
||||
|
||||
### When to use an attribute selector
|
||||
|
||||
You should consider an attribute selector when you want to create a component on a standard native
|
||||
|
|
|
|||
|
|
@ -38,18 +38,18 @@ That service in turn might depend on the `HttpClient` service to fetch heroes as
|
|||
import { inject } from "@angular/core";
|
||||
|
||||
export class HeroService {
|
||||
private heroes: Hero[] = [];
|
||||
private heroes: Hero[] = [];
|
||||
|
||||
private backend = inject(BackendService);
|
||||
private logger = inject(Logger);
|
||||
private backend = inject(BackendService);
|
||||
private logger = inject(Logger);
|
||||
|
||||
async getHeroes() {
|
||||
// Fetch
|
||||
this.heroes = await this.backend.getAll(Hero);
|
||||
// Log
|
||||
this.logger.log(`Fetched ${this.heroes.length} heroes.`);
|
||||
return this.heroes;
|
||||
}
|
||||
async getHeroes() {
|
||||
// Fetch
|
||||
this.heroes = await this.backend.getAll(Hero);
|
||||
// Log
|
||||
this.logger.log(`Fetched ${this.heroes.length} heroes.`);
|
||||
return this.heroes;
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ This command creates the following default `HeroService`:
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class HeroService {}
|
||||
</docs-code>
|
||||
|
|
@ -86,14 +86,14 @@ import { Injectable } from '@angular/core';
|
|||
import { HEROES } from './mock-heroes';
|
||||
|
||||
@Injectable({
|
||||
// declares that this service should be created
|
||||
// by the root application injector.
|
||||
providedIn: 'root',
|
||||
// declares that this service should be created
|
||||
// by the root application injector.
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class HeroService {
|
||||
getHeroes() {
|
||||
return HEROES;
|
||||
}
|
||||
getHeroes() {
|
||||
return HEROES;
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ The type of `heroService` is `HeroService`.
|
|||
import { inject } from "@angular/core";
|
||||
|
||||
export class HeroListComponent {
|
||||
private heroService = inject(HeroService);
|
||||
private heroService = inject(HeroService);
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -134,15 +134,15 @@ import { HEROES } from './mock-heroes';
|
|||
import { Logger } from '../logger.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class HeroService {
|
||||
private logger = inject(Logger);
|
||||
private logger = inject(Logger);
|
||||
|
||||
getHeroes() {
|
||||
this.logger.log('Getting heroes.');
|
||||
return HEROES;
|
||||
}
|
||||
getHeroes() {
|
||||
this.logger.log('Getting heroes.');
|
||||
return HEROES;
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ NOTE: For basic concepts about injector hierarchy and provider scoping, see the
|
|||
|
||||
Angular has two injector hierarchies:
|
||||
|
||||
| Injector hierarchies | Details |
|
||||
|:--- |:--- |
|
||||
| `EnvironmentInjector` hierarchy | Configure an `EnvironmentInjector` in this hierarchy using `@Injectable()` or `providers` array in `ApplicationConfig`. |
|
||||
| `ElementInjector` hierarchy | Created implicitly at each DOM element. An `ElementInjector` is empty by default unless you configure it in the `providers` property on `@Directive()` or `@Component()`. |
|
||||
| Injector hierarchies | Details |
|
||||
| :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `EnvironmentInjector` hierarchy | Configure an `EnvironmentInjector` in this hierarchy using `@Injectable()` or `providers` array in `ApplicationConfig`. |
|
||||
| `ElementInjector` hierarchy | Created implicitly at each DOM element. An `ElementInjector` is empty by default unless you configure it in the `providers` property on `@Directive()` or `@Component()`. |
|
||||
|
||||
<docs-callout title="NgModule Based Applications">
|
||||
For `NgModule` based applications, you can provide dependencies with the `ModuleInjector` hierarchy using an `@NgModule()` or `@Injectable()` annotation.
|
||||
|
|
@ -21,8 +21,8 @@ For `NgModule` based applications, you can provide dependencies with the `Module
|
|||
|
||||
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
|
||||
- The `@Injectable()` `providedIn` property to refer to `root` or `platform`
|
||||
- The `ApplicationConfig` `providers` array
|
||||
|
||||
<docs-callout title="Tree-shaking and @Injectable()">
|
||||
|
||||
|
|
@ -40,10 +40,10 @@ 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' // <--provides this service in the root EnvironmentInjector
|
||||
})
|
||||
export class ItemService {
|
||||
name = 'telephone';
|
||||
name = 'telephone';
|
||||
}
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -55,8 +55,8 @@ The `providedIn` property configures a specific `EnvironmentInjector`, here `roo
|
|||
|
||||
In the case of `NgModule` based applications, the ModuleInjector can be configured in one of two ways by using:
|
||||
|
||||
* The `@Injectable()` `providedIn` property to refer to `root` or `platform`
|
||||
* The `@NgModule()` `providers` array
|
||||
- The `@Injectable()` `providedIn` property to refer to `root` or `platform`
|
||||
- The `@NgModule()` `providers` array
|
||||
|
||||
`ModuleInjector` is configured by the `@NgModule.providers` and `NgModule.imports` property. `ModuleInjector` is a flattening of all the providers arrays that can be reached by following the `NgModule.imports` recursively.
|
||||
|
||||
|
|
@ -178,17 +178,17 @@ Import each of them from `@angular/core` and use each in the `inject` configurat
|
|||
|
||||
Resolution modifiers fall into three categories:
|
||||
|
||||
* What to do if Angular doesn't find what you're looking for, that is `optional`
|
||||
* Where to start looking, that is `skipSelf`
|
||||
* Where to stop looking, `host` and `self`
|
||||
- What to do if Angular doesn't find what you're looking for, that is `optional`
|
||||
- Where to start looking, that is `skipSelf`
|
||||
- Where to stop looking, `host` and `self`
|
||||
|
||||
By default, Angular always starts at the current `Injector` and keeps searching all the way up.
|
||||
Modifiers allow you to change the starting, or _self_, location and the ending location.
|
||||
|
||||
Additionally, you can combine all of the modifiers except:
|
||||
|
||||
* `host` and `self`
|
||||
* `skipSelf` and `self`.
|
||||
- `host` and `self`
|
||||
- `skipSelf` and `self`.
|
||||
|
||||
### `optional`
|
||||
|
||||
|
|
@ -214,12 +214,12 @@ For example, in the following `SelfNoDataComponent`, notice the injected `LeafSe
|
|||
<docs-code header="src/app/self-no-data/self-no-data.component.ts" language="typescript"
|
||||
highlight="[7]">
|
||||
@Component({
|
||||
selector: 'app-self-no-data',
|
||||
templateUrl: './self-no-data.component.html',
|
||||
styleUrls: ['./self-no-data.component.css']
|
||||
selector: 'app-self-no-data',
|
||||
templateUrl: './self-no-data.component.html',
|
||||
styleUrls: ['./self-no-data.component.css']
|
||||
})
|
||||
export class SelfNoDataComponent {
|
||||
public leaf = inject(LeafService, {optional: true, self: true});
|
||||
public leaf = inject(LeafService, {optional: true, self: true});
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -250,15 +250,15 @@ This is when you'd use `skipSelf`:
|
|||
<docs-code header="src/app/skipself/skipself.component.ts" language="typescript"
|
||||
highlight="[[6],[10]]">
|
||||
@Component({
|
||||
selector: 'app-skipself',
|
||||
templateUrl: './skipself.component.html',
|
||||
styleUrls: ['./skipself.component.css'],
|
||||
// Angular would ignore this LeafService instance
|
||||
providers: [{ provide: LeafService, useValue: { emoji: '🍁' } }]
|
||||
selector: 'app-skipself',
|
||||
templateUrl: './skipself.component.html',
|
||||
styleUrls: ['./skipself.component.css'],
|
||||
// Angular would ignore this LeafService instance
|
||||
providers: [{ provide: LeafService, useValue: { emoji: '🍁' } }]
|
||||
})
|
||||
export class SkipselfComponent {
|
||||
// Use skipSelf as inject option
|
||||
public leaf = inject(LeafService, {skipSelf: true});
|
||||
// Use skipSelf as inject option
|
||||
public leaf = inject(LeafService, {skipSelf: true});
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -289,21 +289,21 @@ Use `host` as follows:
|
|||
<docs-code header="src/app/host/host.component.ts" language="typescript"
|
||||
highlight="[[6],[10]]">
|
||||
@Component({
|
||||
selector: 'app-host',
|
||||
templateUrl: './host.component.html',
|
||||
styleUrls: ['./host.component.css'],
|
||||
// provide the service
|
||||
providers: [{ provide: FlowerService, useValue: { emoji: '🌷' } }]
|
||||
selector: 'app-host',
|
||||
templateUrl: './host.component.html',
|
||||
styleUrls: ['./host.component.css'],
|
||||
// provide the service
|
||||
providers: [{ provide: FlowerService, useValue: { emoji: '🌷' } }]
|
||||
})
|
||||
export class HostComponent {
|
||||
// use host when injecting the service
|
||||
flower = inject(FlowerService, {host: true, optional: true});
|
||||
// use host when injecting the service
|
||||
flower = inject(FlowerService, {host: true, optional: true});
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
Since `HostComponent` has the `host` option , no matter what the parent of `HostComponent` might have as a `flower.emoji` value, the `HostComponent` will use tulip <code>🌷</code>.
|
||||
|
||||
### Modifiers with constructor injection
|
||||
### Modifiers with constructor injection
|
||||
|
||||
Similarly as presented before, the behavior of constructor injection can be modified with `@Optional()`, `@Self()`, `@SkipSelf()` and `@Host()`.
|
||||
|
||||
|
|
@ -357,8 +357,8 @@ The following sections demonstrate `providers` and `viewProviders` along with wa
|
|||
|
||||
A component class can provide services in two ways:
|
||||
|
||||
| Arrays | Details |
|
||||
|:--- |:--- |
|
||||
| Arrays | Details |
|
||||
| :--------------------------- | :--------------------------------------------- |
|
||||
| With a `providers` array | `@Component({ providers: [SomeService] })` |
|
||||
| With a `viewProviders` array | `@Component({ viewProviders: [SomeService] })` |
|
||||
|
||||
|
|
@ -369,11 +369,11 @@ For example, the logical tree will show that `<child-component>` is a direct chi
|
|||
In the logical tree, you will see special attributes: `@Provide`, `@Inject`, and `@ApplicationConfig`.
|
||||
These aren't real attributes but are here to demonstrate what is going on under the hood.
|
||||
|
||||
| Angular service attribute | Details |
|
||||
|:--- |:--- |
|
||||
| `@Inject(Token)=>Value` | If `Token` is injected at this location in the logical tree, its value would be `Value`. |
|
||||
| `@Provide(Token=Value)` | Indicates that `Token` is provided with `Value` at this location in the logical tree. |
|
||||
| `@ApplicationConfig` | Demonstrates that a fallback `EnvironmentInjector` should be used at this location. |
|
||||
| Angular service attribute | Details |
|
||||
| :------------------------ | :--------------------------------------------------------------------------------------- |
|
||||
| `@Inject(Token)=>Value` | If `Token` is injected at this location in the logical tree, its value would be `Value`. |
|
||||
| `@Provide(Token=Value)` | Indicates that `Token` is provided with `Value` at this location in the logical tree. |
|
||||
| `@ApplicationConfig` | Demonstrates that a fallback `EnvironmentInjector` should be used at this location. |
|
||||
|
||||
### Example app structure
|
||||
|
||||
|
|
@ -394,8 +394,8 @@ 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-child> <!-- ChildComponent selector -->
|
||||
</app-child>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -458,19 +458,18 @@ When `<app-root>` requests the `FlowerService`, it is the injector's job to reso
|
|||
The resolution of the token happens in two phases:
|
||||
|
||||
1. The injector determines the starting location in the logical tree and an ending location of the search.
|
||||
The injector begins with the starting location and looks for the token at each view level in the logical tree.
|
||||
If the token is found it is returned.
|
||||
The injector begins with the starting location and looks for the token at each view level in the logical tree.
|
||||
If the token is found it is returned.
|
||||
|
||||
1. If the token is not found, the injector looks for the closest parent `EnvironmentInjector` to delegate the request to.
|
||||
|
||||
In the example case, the constraints are:
|
||||
|
||||
1. Start with `<#VIEW>` belonging to `<app-root>` and end with `<app-root>`.
|
||||
|
||||
* Normally the starting point for search is at the point of injection.
|
||||
However, in this case `<app-root>` is a component. `@Component`s are special in that they also include their own `viewProviders`, which is why the search starts at `<#VIEW>` belonging to `<app-root>`.
|
||||
This would not be the case for a directive matched at the same location.
|
||||
* The ending location happens to be the same as the component itself, because it is the topmost component in this application.
|
||||
- Normally the starting point for search is at the point of injection.
|
||||
However, in this case `<app-root>` is a component. `@Component`s are special in that they also include their own `viewProviders`, which is why the search starts at `<#VIEW>` belonging to `<app-root>`.
|
||||
This would not be the case for a directive matched at the same location.
|
||||
- The ending location happens to be the same as the component itself, because it is the topmost component in this application.
|
||||
|
||||
1. The `EnvironmentInjector` provided by the `ApplicationConfig` acts as the fallback injector when the injection token can't be found in the `ElementInjector` hierarchies.
|
||||
|
||||
|
|
@ -481,15 +480,15 @@ Now, in the `ChildComponent` class, add a provider for `FlowerService` to demons
|
|||
<docs-code header="src/app/child.component.ts" language="typescript"
|
||||
highlight="[[5,6],[10]]">
|
||||
@Component({
|
||||
selector: 'app-child',
|
||||
templateUrl: './child.component.html',
|
||||
styleUrls: ['./child.component.css'],
|
||||
// use the providers array to provide a service
|
||||
providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }]
|
||||
selector: 'app-child',
|
||||
templateUrl: './child.component.html',
|
||||
styleUrls: ['./child.component.css'],
|
||||
// use the providers array to provide a service
|
||||
providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }]
|
||||
})
|
||||
export class ChildComponent {
|
||||
// inject the service
|
||||
flower = inject(FlowerService);
|
||||
// inject the service
|
||||
flower = inject(FlowerService);
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -514,17 +513,18 @@ In the logical tree, this is represented as follows:
|
|||
<docs-code language="html">
|
||||
|
||||
<app-root @ApplicationConfig
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW>
|
||||
<p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>
|
||||
<app-child @Provide(FlowerService="🌻")
|
||||
@Inject(FlowerService)=>"🌻"> <!-- search ends here -->
|
||||
<#VIEW> <!-- search starts here -->
|
||||
<h2>Child Component</h2>
|
||||
<p>Emoji from FlowerService: {{flower.emoji}} (🌻)</p>
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW>
|
||||
|
||||
<p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>
|
||||
<app-child @Provide(FlowerService="🌻")
|
||||
@Inject(FlowerService)=>"🌻"> <!-- search ends here -->
|
||||
<#VIEW> <!-- search starts here -->
|
||||
<h2>Child Component</h2>
|
||||
<p>Emoji from FlowerService: {{flower.emoji}} (🌻)</p>
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -551,10 +551,10 @@ First, create an `AnimalService` with an `emoji` property of whale <code>🐳</c
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AnimalService {
|
||||
emoji = '🐳';
|
||||
emoji = '🐳';
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -575,17 +575,17 @@ Here, it has a value of dog 🐶.
|
|||
<docs-code header="src/app/child.component.ts" language="typescript"
|
||||
highlight="[[7],[11]]">
|
||||
@Component({
|
||||
selector: 'app-child',
|
||||
templateUrl: './child.component.html',
|
||||
styleUrls: ['./child.component.css'],
|
||||
// provide services
|
||||
providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }],
|
||||
viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }]
|
||||
selector: 'app-child',
|
||||
templateUrl: './child.component.html',
|
||||
styleUrls: ['./child.component.css'],
|
||||
// provide services
|
||||
providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }],
|
||||
viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }]
|
||||
})
|
||||
export class ChildComponent {
|
||||
// inject services
|
||||
flower = inject(FlowerService);
|
||||
animal = inject(AnimalService)
|
||||
// inject services
|
||||
flower = inject(FlowerService);
|
||||
animal = inject(AnimalService)
|
||||
...
|
||||
}
|
||||
</docs-code>
|
||||
|
|
@ -620,16 +620,17 @@ The logic tree for this example of `viewProviders` is as follows:
|
|||
<docs-code language="html">
|
||||
|
||||
<app-root @ApplicationConfig
|
||||
@Inject(AnimalService) animal=>"🐳">
|
||||
<#VIEW>
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
@Inject(AnimalService=>"🐶")>
|
||||
<!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
|
||||
<p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
@Inject(AnimalService) animal=>"🐳">
|
||||
<#VIEW>
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
@Inject(AnimalService=>"🐶")>
|
||||
|
||||
<!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
|
||||
<p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -667,8 +668,8 @@ Remember to add the `InspectorComponent` to the `ChildComponent` `imports` array
|
|||
<docs-code header="src/app/child/child.component.ts" language="typescript"
|
||||
highlight="[3]">
|
||||
@Component({
|
||||
...
|
||||
imports: [InspectorComponent]
|
||||
...
|
||||
imports: [InspectorComponent]
|
||||
})
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -723,13 +724,14 @@ The `AnimalService` in the logical tree would look like this:
|
|||
<docs-code language="html">
|
||||
|
||||
<app-root @ApplicationConfig
|
||||
@Inject(AnimalService) animal=>"🐳">
|
||||
<#VIEW>
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
@Inject(AnimalService=>"🐶")>
|
||||
<!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
|
||||
<p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>
|
||||
@Inject(AnimalService) animal=>"🐳">
|
||||
<#VIEW>
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
@Inject(AnimalService=>"🐶")>
|
||||
|
||||
<!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
|
||||
<p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>
|
||||
|
||||
<div class="container">
|
||||
<h3>Content projection</h3>
|
||||
|
|
@ -745,7 +747,8 @@ The `AnimalService` in the logical tree would look like this:
|
|||
</app-inspector>
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
|
||||
</#VIEW>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -781,14 +784,16 @@ In a logical tree, this same idea might look like this:
|
|||
<docs-code language="html">
|
||||
|
||||
<app-root @ApplicationConfig
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW>
|
||||
<app-child @Provide(FlowerService="🌻")>
|
||||
<#VIEW @Inject(FlowerService, SkipSelf)=>"🌺">
|
||||
<!-- With SkipSelf, the injector looks to the next injector up the tree (app-root) -->
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW>
|
||||
<app-child @Provide(FlowerService="🌻")>
|
||||
<#VIEW @Inject(FlowerService, SkipSelf)=>"🌺">
|
||||
|
||||
<!-- With SkipSelf, the injector looks to the next injector up the tree (app-root) -->
|
||||
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -802,13 +807,13 @@ Here's the idea in the logical tree:
|
|||
<docs-code language="html">
|
||||
|
||||
<app-root @ApplicationConfig
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW> <!-- end search here with null-->
|
||||
<app-child @Provide(FlowerService="🌻")> <!-- start search here -->
|
||||
<#VIEW inject(FlowerService, {skipSelf: true, host: true, optional:true})=>null>
|
||||
</#VIEW>
|
||||
</app-parent>
|
||||
</#VIEW>
|
||||
@Inject(FlowerService) flower=>"🌺">
|
||||
<#VIEW> <!-- end search here with null-->
|
||||
<app-child @Provide(FlowerService="🌻")> <!-- start search here -->
|
||||
<#VIEW inject(FlowerService, {skipSelf: true, host: true, optional:true})=>null>
|
||||
</#VIEW>
|
||||
</app-parent>
|
||||
</#VIEW>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -820,7 +825,7 @@ Here, the services and their values are the same, but `host` stops the injector
|
|||
Remember, `<app-child>` provides the `AnimalService` in the `viewProviders` array with the value of dog <code>🐶</code>.
|
||||
Because the injector has only to look at the `ElementInjector` of the `<app-child>` for the `AnimalService`, it never sees the whale <code>🐳</code>.
|
||||
|
||||
As in the `FlowerService` example, if you add `skipSelf` to the `inject()` of `AnimalService`, the injector won't look in the `ElementInjector` of the current `<app-child>` for the `AnimalService`.
|
||||
As in the `FlowerService` example, if you add `skipSelf` to the `inject()` of `AnimalService`, the injector won't look in the `ElementInjector` of the current `<app-child>` for the `AnimalService`.
|
||||
Instead, the injector will begin at the `<app-root>` `ElementInjector`.
|
||||
|
||||
<docs-code language="typescript" highlight="[5]">
|
||||
|
|
@ -838,15 +843,17 @@ The logical tree looks like this with `skipSelf` in `<app-child>`:
|
|||
<docs-code language="html">
|
||||
|
||||
<app-root @ApplicationConfig
|
||||
@Inject(AnimalService=>"🐳")>
|
||||
<#VIEW><!-- search begins here -->
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
@Inject(AnimalService, SkipSelf=>"🐳")>
|
||||
<!--Add skipSelf -->
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
@Inject(AnimalService=>"🐳")>
|
||||
<#VIEW><!-- search begins here -->
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
@Inject(AnimalService, SkipSelf=>"🐳")>
|
||||
|
||||
<!--Add skipSelf -->
|
||||
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -877,14 +884,14 @@ export class ChildComponent {
|
|||
<docs-code language="html">
|
||||
|
||||
<app-root @ApplicationConfig
|
||||
@Inject(AnimalService=>"🐳")>
|
||||
<#VIEW>
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
inject(AnimalService, {host: true}=>"🐶")> <!-- host stops search here -->
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
@Inject(AnimalService=>"🐳")>
|
||||
<#VIEW>
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
inject(AnimalService, {host: true}=>"🐶")> <!-- host stops search here -->
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -927,17 +934,18 @@ The logical tree representation shows why this is:
|
|||
<docs-code language="html">
|
||||
|
||||
<app-root @ApplicationConfig
|
||||
@Inject(AnimalService=>"🐳")>
|
||||
<#VIEW @Provide(AnimalService="🦔")
|
||||
@Inject(AnimalService, @Optional)=>"🦔">
|
||||
<!-- ^^skipSelf starts here, host stops here^^ -->
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
inject(AnimalService, {skipSelf:true, host: true, optional: true})=>"🦔">
|
||||
<!-- Add skipSelf ^^-->
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
@Inject(AnimalService=>"🐳")>
|
||||
<#VIEW @Provide(AnimalService="🦔")
|
||||
@Inject(AnimalService, @Optional)=>"🦔">
|
||||
|
||||
<!-- ^^skipSelf starts here, host stops here^^ -->
|
||||
<app-child>
|
||||
<#VIEW @Provide(AnimalService="🐶")
|
||||
inject(AnimalService, {skipSelf:true, host: true, optional: true})=>"🦔">
|
||||
<!-- Add skipSelf ^^-->
|
||||
</#VIEW>
|
||||
</app-child>
|
||||
</#VIEW>
|
||||
</app-root>
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -963,9 +971,9 @@ Instead, you should provide the `VillainsService` in the `providers` metadata of
|
|||
<docs-code header="src/app/villains-list.component.ts (metadata)" language="typescript"
|
||||
highlight="[4]">
|
||||
@Component({
|
||||
selector: 'app-villains-list',
|
||||
templateUrl: './villains-list.component.html',
|
||||
providers: [VillainsService]
|
||||
selector: 'app-villains-list',
|
||||
templateUrl: './villains-list.component.html',
|
||||
providers: [VillainsService]
|
||||
})
|
||||
export class VillainsListComponent {}
|
||||
</docs-code>
|
||||
|
|
@ -987,9 +995,9 @@ Each selected hero tax return opens in its own component and multiple returns ca
|
|||
|
||||
Each tax return component has the following characteristics:
|
||||
|
||||
* Is its own tax return editing session
|
||||
* Can change a tax return without affecting a return in another component
|
||||
* Has the ability to save the changes to its tax return or cancel them
|
||||
- Is its own tax return editing session
|
||||
- Can change a tax return without affecting a return in another component
|
||||
- Has the ability to save the changes to its tax return or cancel them
|
||||
|
||||
Suppose that the `HeroTaxReturnComponent` had logic to manage and restore changes.
|
||||
That would be a straightforward task for a hero tax return.
|
||||
|
|
@ -1006,28 +1014,28 @@ import { HeroesService } from './heroes.service';
|
|||
|
||||
@Injectable()
|
||||
export class HeroTaxReturnService {
|
||||
private currentTaxReturn!: HeroTaxReturn;
|
||||
private originalTaxReturn!: HeroTaxReturn;
|
||||
private currentTaxReturn!: HeroTaxReturn;
|
||||
private originalTaxReturn!: HeroTaxReturn;
|
||||
|
||||
private heroService = inject(HeroesService);
|
||||
private heroService = inject(HeroesService);
|
||||
|
||||
set taxReturn(htr: HeroTaxReturn) {
|
||||
this.originalTaxReturn = htr;
|
||||
this.currentTaxReturn = htr.clone();
|
||||
}
|
||||
set taxReturn(htr: HeroTaxReturn) {
|
||||
this.originalTaxReturn = htr;
|
||||
this.currentTaxReturn = htr.clone();
|
||||
}
|
||||
|
||||
get taxReturn(): HeroTaxReturn {
|
||||
return this.currentTaxReturn;
|
||||
}
|
||||
get taxReturn(): HeroTaxReturn {
|
||||
return this.currentTaxReturn;
|
||||
}
|
||||
|
||||
restoreTaxReturn() {
|
||||
this.taxReturn = this.originalTaxReturn;
|
||||
}
|
||||
restoreTaxReturn() {
|
||||
this.taxReturn = this.originalTaxReturn;
|
||||
}
|
||||
|
||||
saveTaxReturn() {
|
||||
this.taxReturn = this.currentTaxReturn;
|
||||
this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe();
|
||||
}
|
||||
saveTaxReturn() {
|
||||
this.taxReturn = this.currentTaxReturn;
|
||||
this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe();
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -1039,46 +1047,46 @@ import { HeroTaxReturn } from './hero';
|
|||
import { HeroTaxReturnService } from './hero-tax-return.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-tax-return',
|
||||
templateUrl: './hero-tax-return.component.html',
|
||||
styleUrls: [ './hero-tax-return.component.css' ],
|
||||
providers: [ HeroTaxReturnService ]
|
||||
selector: 'app-hero-tax-return',
|
||||
templateUrl: './hero-tax-return.component.html',
|
||||
styleUrls: [ './hero-tax-return.component.css' ],
|
||||
providers: [ HeroTaxReturnService ]
|
||||
})
|
||||
export class HeroTaxReturnComponent {
|
||||
message = '';
|
||||
message = '';
|
||||
|
||||
close = output<void>();
|
||||
close = output<void>();
|
||||
|
||||
get taxReturn(): HeroTaxReturn {
|
||||
return this.heroTaxReturnService.taxReturn;
|
||||
}
|
||||
get taxReturn(): HeroTaxReturn {
|
||||
return this.heroTaxReturnService.taxReturn;
|
||||
}
|
||||
|
||||
taxReturn = input.required<HeroTaxReturn>();
|
||||
taxReturn = input.required<HeroTaxReturn>();
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.heroTaxReturnService.taxReturn = this.taxReturn();
|
||||
})
|
||||
}
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.heroTaxReturnService.taxReturn = this.taxReturn();
|
||||
})
|
||||
}
|
||||
|
||||
private heroTaxReturnService = inject(HeroTaxReturnService);
|
||||
private heroTaxReturnService = inject(HeroTaxReturnService);
|
||||
|
||||
onCanceled() {
|
||||
this.flashMessage('Canceled');
|
||||
this.heroTaxReturnService.restoreTaxReturn();
|
||||
}
|
||||
onCanceled() {
|
||||
this.flashMessage('Canceled');
|
||||
this.heroTaxReturnService.restoreTaxReturn();
|
||||
}
|
||||
|
||||
onClose() { this.close.emit(); }
|
||||
onClose() { this.close.emit(); }
|
||||
|
||||
onSaved() {
|
||||
this.flashMessage('Saved');
|
||||
this.heroTaxReturnService.saveTaxReturn();
|
||||
}
|
||||
onSaved() {
|
||||
this.flashMessage('Saved');
|
||||
this.heroTaxReturnService.saveTaxReturn();
|
||||
}
|
||||
|
||||
flashMessage(msg: string) {
|
||||
this.message = msg;
|
||||
setTimeout(() => this.message = '', 500);
|
||||
}
|
||||
flashMessage(msg: string) {
|
||||
this.message = msg;
|
||||
setTimeout(() => this.message = '', 500);
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -1110,7 +1118,7 @@ For example, consider a `Car` component that includes tire service information a
|
|||
|
||||
The root injector, marked as (A), uses _generic_ providers for details about `CarService` and `EngineService`.
|
||||
|
||||
1. `Car` component (A). Component (A) displays tire service data about a car and specifies generic services to provide more information about the car.
|
||||
1. `Car` component (A). Component (A) displays tire service data about a car and specifies generic services to provide more information about the car.
|
||||
|
||||
2. Child component (B). Component (B) defines its own, _specialized_ providers for `CarService` and `EngineService` that have special capabilities suitable for what's going on in component (B).
|
||||
|
||||
|
|
@ -1135,9 +1143,9 @@ Behind the scenes, each component sets up its own injector with zero, one, or mo
|
|||
|
||||
When you resolve an instance of `Car` at the deepest component (C), its injector produces:
|
||||
|
||||
* An instance of `Car` resolved by injector (C)
|
||||
* An `Engine` resolved by injector (B)
|
||||
* Its `Tires` resolved by the root injector (A).
|
||||
- An instance of `Car` resolved by injector (C)
|
||||
- An `Engine` resolved by injector (B)
|
||||
- Its `Tires` resolved by the root injector (A).
|
||||
|
||||
```mermaid
|
||||
graph BT;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Optimizing client application size with lightweight injection tokens
|
||||
|
||||
This page provides a conceptual overview of a dependency injection technique that is recommended for library developers.
|
||||
Designing your library with *lightweight injection tokens* helps optimize the bundle size of client applications that use your library.
|
||||
Designing your library with _lightweight injection tokens_ helps optimize the bundle size of client applications that use your library.
|
||||
|
||||
You can manage the dependency structure among your components and injectable services to optimize bundle size by using tree-shakable providers.
|
||||
This normally ensures that if a provided component or service is never actually used by the application, the compiler can remove its code from the bundle.
|
||||
|
|
@ -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>…</lib-header>;
|
||||
</lib-card>;
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -40,11 +40,11 @@ In a likely implementation, the `<lib-card>` component uses `@ContentChild()` or
|
|||
class LibHeaderComponent {}
|
||||
|
||||
@Component({
|
||||
selector: 'lib-card',
|
||||
…,
|
||||
selector: 'lib-card',
|
||||
…,
|
||||
})
|
||||
class LibCardComponent {
|
||||
@ContentChild(LibHeaderComponent) header: LibHeaderComponent|null = null;
|
||||
@ContentChild(LibHeaderComponent) header: LibHeaderComponent|null = null;
|
||||
}
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -57,13 +57,13 @@ This is because `LibCardComponent` actually contains two references to the `LibH
|
|||
@ContentChild(LibHeaderComponent) header: LibHeaderComponent;
|
||||
</docs-code>
|
||||
|
||||
* One of these reference is in the *type position*-- that is, it specifies `LibHeaderComponent` as a type: `header: LibHeaderComponent;`.
|
||||
* The other reference is in the *value position*-- that is, LibHeaderComponent is the value of the `@ContentChild()` parameter decorator: `@ContentChild(LibHeaderComponent)`.
|
||||
- One of these reference is in the _type position_-- that is, it specifies `LibHeaderComponent` as a type: `header: LibHeaderComponent;`.
|
||||
- The other reference is in the _value position_-- that is, LibHeaderComponent is the value of the `@ContentChild()` parameter decorator: `@ContentChild(LibHeaderComponent)`.
|
||||
|
||||
The compiler handles token references in these positions differently:
|
||||
|
||||
* The compiler erases *type position* references after conversion from TypeScript, so they have no impact on tree-shaking.
|
||||
* The compiler must keep *value position* references at runtime, which **prevents** the component from being tree-shaken.
|
||||
- The compiler erases _type position_ references after conversion from TypeScript, so they have no impact on tree-shaking.
|
||||
- The compiler must keep _value position_ references at runtime, which **prevents** the component from being tree-shaken.
|
||||
|
||||
In the example, the compiler retains the `LibHeaderComponent` token that occurs in the value position.
|
||||
This prevents the referenced component from being tree-shaken, even if the application does not actually use `<lib-header>` anywhere.
|
||||
|
|
@ -74,8 +74,8 @@ If `LibHeaderComponent` 's code, template, and styles combine to become too larg
|
|||
The tree-shaking problem arises when a component is used as an injection token.
|
||||
There are two cases when that can happen:
|
||||
|
||||
* The token is used in the value position of a [content query](guide/components/queries#content-queries).
|
||||
* The token is used as a type specifier for constructor injection.
|
||||
- The token is used in the value position of a [content query](guide/components/queries#content-queries).
|
||||
- The token is used as a type specifier for constructor injection.
|
||||
|
||||
In the following example, both uses of the `OtherComponent` token cause retention of `OtherComponent`, preventing it from being tree-shaken when it is not used:
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ In the following example, both uses of the `OtherComponent` token cause retentio
|
|||
class MyComponent {
|
||||
constructor(@Optional() other: OtherComponent) {}
|
||||
|
||||
@ContentChild(OtherComponent) other: OtherComponent|null;
|
||||
@ContentChild(OtherComponent) other: OtherComponent|null;
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -104,20 +104,20 @@ The following example shows how this works for the `LibHeaderComponent`:
|
|||
abstract class LibHeaderToken {}
|
||||
|
||||
@Component({
|
||||
selector: 'lib-header',
|
||||
providers: [
|
||||
{provide: LibHeaderToken, useExisting: LibHeaderComponent}
|
||||
]
|
||||
…,
|
||||
selector: 'lib-header',
|
||||
providers: [
|
||||
{provide: LibHeaderToken, useExisting: LibHeaderComponent}
|
||||
]
|
||||
…,
|
||||
})
|
||||
class LibHeaderComponent extends LibHeaderToken {}
|
||||
|
||||
@Component({
|
||||
selector: 'lib-card',
|
||||
…,
|
||||
selector: 'lib-card',
|
||||
…,
|
||||
})
|
||||
class LibCardComponent {
|
||||
@ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
|
||||
@ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -152,30 +152,30 @@ abstract class LibHeaderToken {
|
|||
}
|
||||
|
||||
@Component({
|
||||
selector: 'lib-header',
|
||||
providers: [
|
||||
{provide: LibHeaderToken, useExisting: LibHeaderComponent}
|
||||
]
|
||||
…,
|
||||
selector: 'lib-header',
|
||||
providers: [
|
||||
{provide: LibHeaderToken, useExisting: LibHeaderComponent}
|
||||
]
|
||||
…,
|
||||
})
|
||||
class LibHeaderComponent extends LibHeaderToken {
|
||||
doSomething(): void {
|
||||
// Concrete implementation of `doSomething`
|
||||
}
|
||||
doSomething(): void {
|
||||
// Concrete implementation of `doSomething`
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'lib-card',
|
||||
…,
|
||||
selector: 'lib-card',
|
||||
…,
|
||||
})
|
||||
class LibCardComponent implement AfterContentInit {
|
||||
@ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
|
||||
@ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
if (this.header !== null) {
|
||||
this.header?.doSomething();
|
||||
}
|
||||
}
|
||||
ngAfterContentInit(): void {
|
||||
if (this.header !== null) {
|
||||
this.header?.doSomething();
|
||||
}
|
||||
}
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,28 +8,28 @@ This section walks you through creating a highlight directive that sets the back
|
|||
|
||||
1. To create a directive, use the CLI command [`ng generate directive`](tools/cli/schematics).
|
||||
|
||||
<docs-code language="shell">
|
||||
<docs-code language="shell">
|
||||
|
||||
ng generate directive highlight
|
||||
ng generate directive highlight
|
||||
|
||||
</docs-code>
|
||||
</docs-code>
|
||||
|
||||
The CLI creates `src/app/highlight.directive.ts`, a corresponding test file `src/app/highlight.directive.spec.ts`.
|
||||
The CLI creates `src/app/highlight.directive.ts`, a corresponding test file `src/app/highlight.directive.spec.ts`.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.0.ts"/>
|
||||
<docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.0.ts"/>
|
||||
|
||||
The `@Directive()` decorator's configuration property specifies the directive's CSS attribute selector, `[appHighlight]`.
|
||||
The `@Directive()` decorator's configuration property specifies the directive's CSS attribute selector, `[appHighlight]`.
|
||||
|
||||
1. Import `ElementRef` from `@angular/core`.
|
||||
`ElementRef` grants direct access to the host DOM element through its `nativeElement` property.
|
||||
`ElementRef` grants direct access to the host DOM element through its `nativeElement` property.
|
||||
|
||||
1. Add `ElementRef` in the directive's `constructor()` to [inject](guide/di) a reference to the host DOM element, the element to which you apply `appHighlight`.
|
||||
|
||||
1. Add logic to the `HighlightDirective` class that sets the background to yellow.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.1.ts"/>
|
||||
<docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.1.ts"/>
|
||||
|
||||
HELPFUL: Directives *do not* support namespaces.
|
||||
HELPFUL: Directives _do not_ support namespaces.
|
||||
|
||||
<docs-code header="src/app/app.component.avoid.html (unsupported)" path="adev/src/content/examples/attribute-directives/src/app/app.component.avoid.html" visibleRegion="unsupported"/>
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ HELPFUL: Directives *do not* support namespaces.
|
|||
|
||||
1. To use the `HighlightDirective`, add a `<p>` element to the HTML template with the directive as an attribute.
|
||||
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/attribute-directives/src/app/app.component.1.html" visibleRegion="applied"/>
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/attribute-directives/src/app/app.component.1.html" visibleRegion="applied"/>
|
||||
|
||||
Angular creates an instance of the `HighlightDirective` class and injects a reference to the `<p>` element into the directive's constructor, which sets the `<p>` element's background style to yellow.
|
||||
|
||||
|
|
@ -47,11 +47,11 @@ This section shows you how to detect when a user mouses into or out of the eleme
|
|||
|
||||
1. Import `HostListener` from '@angular/core'.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts (imports)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.2.ts" visibleRegion="imports"/>
|
||||
<docs-code header="src/app/highlight.directive.ts (imports)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.2.ts" visibleRegion="imports"/>
|
||||
|
||||
1. Add two event handlers that respond when the mouse enters or leaves, each with the `@HostListener()` decorator.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts (mouse-methods)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.2.ts" visibleRegion="mouse-methods"/>
|
||||
<docs-code header="src/app/highlight.directive.ts (mouse-methods)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.2.ts" visibleRegion="mouse-methods"/>
|
||||
|
||||
Subscribe to events of the DOM element that hosts an attribute directive, the `<p>` in this case, with the `@HostListener()` decorator.
|
||||
|
||||
|
|
@ -71,26 +71,25 @@ This section walks you through setting the highlight color while applying the `H
|
|||
|
||||
1. In `highlight.directive.ts`, import `Input` from `@angular/core`.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts (imports)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="imports"/>
|
||||
<docs-code header="src/app/highlight.directive.ts (imports)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="imports"/>
|
||||
|
||||
1. Add an `appHighlight` `input` property.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="input"/>
|
||||
<docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="input"/>
|
||||
|
||||
The `input()` function adds metadata to the class that makes the directive's `appHighlight` property available for binding.
|
||||
The `input()` function adds metadata to the class that makes the directive's `appHighlight` property available for binding.
|
||||
|
||||
2. In `app.component.ts`, add a `color` property to the `AppComponent`.
|
||||
1. In `app.component.ts`, add a `color` property to the `AppComponent`.
|
||||
|
||||
<docs-code header="src/app/app.component.ts (class)" path="adev/src/content/examples/attribute-directives/src/app/app.component.1.ts" visibleRegion="class"/>
|
||||
<docs-code header="src/app/app.component.ts (class)" path="adev/src/content/examples/attribute-directives/src/app/app.component.1.ts" visibleRegion="class"/>
|
||||
|
||||
3. To simultaneously apply the directive and the color, use property binding with the `appHighlight` directive selector, setting it equal to `color`.
|
||||
1. To simultaneously apply the directive and the color, use property binding with the `appHighlight` directive selector, setting it equal to `color`.
|
||||
|
||||
<docs-code header="src/app/app.component.html (color)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="color"/>
|
||||
<docs-code header="src/app/app.component.html (color)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="color"/>
|
||||
|
||||
The `[appHighlight]` attribute binding performs two tasks:
|
||||
|
||||
* Applies the highlighting directive to the `<p>` element
|
||||
* Sets the directive's highlight color with a property binding
|
||||
The `[appHighlight]` attribute binding performs two tasks:
|
||||
- Applies the highlighting directive to the `<p>` element
|
||||
- Sets the directive's highlight color with a property binding
|
||||
|
||||
### Setting the value with user input
|
||||
|
||||
|
|
@ -98,19 +97,19 @@ This section guides you through adding radio buttons to bind your color choice t
|
|||
|
||||
1. Add markup to `app.component.html` for choosing a color as follows:
|
||||
|
||||
<docs-code header="src/app/app.component.html (v2)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="v2"/>
|
||||
<docs-code header="src/app/app.component.html (v2)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="v2"/>
|
||||
|
||||
1. Revise the `AppComponent.color` so that it has no initial value.
|
||||
|
||||
<docs-code header="src/app/app.component.ts (class)" path="adev/src/content/examples/attribute-directives/src/app/app.component.ts" visibleRegion="class"/>
|
||||
<docs-code header="src/app/app.component.ts (class)" path="adev/src/content/examples/attribute-directives/src/app/app.component.ts" visibleRegion="class"/>
|
||||
|
||||
1. In `highlight.directive.ts`, revise `onMouseEnter` method so that it first tries to highlight with `appHighlight` and falls back to `red` if `appHighlight` is `undefined`.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts (mouse-enter)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="mouse-enter"/>
|
||||
<docs-code header="src/app/highlight.directive.ts (mouse-enter)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="mouse-enter"/>
|
||||
|
||||
1. Serve your application to verify that the user can choose the color with the radio buttons.
|
||||
|
||||
<img alt="Animated gif of the refactored highlight directive changing color according to the radio button the user selects" src="assets/images/guide/attribute-directives/highlight-directive-v2-anim.gif">
|
||||
<img alt="Animated gif of the refactored highlight directive changing color according to the radio button the user selects" src="assets/images/guide/attribute-directives/highlight-directive-v2-anim.gif">
|
||||
|
||||
## Binding to a second property
|
||||
|
||||
|
|
@ -118,18 +117,18 @@ This section guides you through configuring your application so the developer ca
|
|||
|
||||
1. Add a second `Input()` property to `HighlightDirective` called `defaultColor`.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts (defaultColor)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.ts" visibleRegion="defaultColor"/>
|
||||
<docs-code header="src/app/highlight.directive.ts (defaultColor)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.ts" visibleRegion="defaultColor"/>
|
||||
|
||||
1. Revise the directive's `onMouseEnter` so that it first tries to highlight with the `appHighlight`, then with the `defaultColor`, and falls back to `red` if both properties are `undefined`.
|
||||
|
||||
<docs-code header="src/app/highlight.directive.ts (mouse-enter)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.ts" visibleRegion="mouse-enter"/>
|
||||
<docs-code header="src/app/highlight.directive.ts (mouse-enter)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.ts" visibleRegion="mouse-enter"/>
|
||||
|
||||
1. To bind to the `AppComponent.color` and fall back to "violet" as the default color, add the following HTML.
|
||||
In this case, the `defaultColor` binding doesn't use square brackets, `[]`, because it is static.
|
||||
In this case, the `defaultColor` binding doesn't use square brackets, `[]`, because it is static.
|
||||
|
||||
<docs-code header="src/app/app.component.html (defaultColor)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="defaultColor"/>
|
||||
<docs-code header="src/app/app.component.html (defaultColor)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="defaultColor"/>
|
||||
|
||||
As with components, you can add multiple directive property bindings to a host element.
|
||||
As with components, you can add multiple directive property bindings to a host element.
|
||||
|
||||
The default color is red if there is no default color binding.
|
||||
When the user chooses a color the selected color becomes the active highlight color.
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
Angular directives offer a great way to encapsulate reusable behaviors— directives can apply
|
||||
attributes, CSS classes, and event listeners to an element.
|
||||
|
||||
The *directive composition API* lets you apply directives to a component's host element from
|
||||
*within* the component TypeScript class.
|
||||
The _directive composition API_ lets you apply directives to a component's host element from
|
||||
_within_ the component TypeScript class.
|
||||
|
||||
## Adding directives to a component
|
||||
|
||||
You apply directives to a component by adding a `hostDirectives` property to a component's
|
||||
decorator. We call such directives *host directives*.
|
||||
decorator. We call such directives _host directives_.
|
||||
|
||||
In this example, we apply the directive `MenuBehavior` to the host element of `AdminMenu`. This
|
||||
works similarly to applying the `MenuBehavior` to the `<admin-menu>` element in a template.
|
||||
|
|
|
|||
|
|
@ -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 )*"
|
||||
_:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )_"
|
||||
|
||||
</docs-code>
|
||||
|
||||
|
|
@ -154,23 +154,23 @@ let = "let" :local "=" :export ";"?
|
|||
|
||||
Angular translates structural directive shorthand into the normal binding syntax as follows:
|
||||
|
||||
| Shorthand | Translation |
|
||||
|:--- |:--- |
|
||||
| `prefix` and naked `expression` | `[prefix]="expression"` |
|
||||
| `keyExp` | `[prefixKey]="expression"` (The `prefix` is added to the `key`) |
|
||||
| `let local` | `let-local="export"` |
|
||||
| Shorthand | Translation |
|
||||
| :------------------------------ | :-------------------------------------------------------------- |
|
||||
| `prefix` and naked `expression` | `[prefix]="expression"` |
|
||||
| `keyExp` | `[prefixKey]="expression"` (The `prefix` is added to the `key`) |
|
||||
| `let local` | `let-local="export"` |
|
||||
|
||||
### Shorthand examples
|
||||
|
||||
The following table provides shorthand examples:
|
||||
|
||||
| Shorthand | How Angular interprets the syntax |
|
||||
|:--- |:--- |
|
||||
| `*myDir="let item of [1,2,3]"` | `<ng-template myDir let-item [myDirOf]="[1, 2, 3]">` |
|
||||
| Shorthand | How Angular interprets the syntax |
|
||||
| :-------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
|
||||
| `*myDir="let item of [1,2,3]"` | `<ng-template myDir let-item [myDirOf]="[1, 2, 3]">` |
|
||||
| `*myDir="let item of [1,2,3] as items; trackBy: myTrack; index as i"` | `<ng-template myDir let-item [myDirOf]="[1,2,3]" let-items="myDirOf" [myDirTrackBy]="myTrack" let-i="index">` |
|
||||
| `*ngComponentOutlet="componentClass";` | `<ng-template [ngComponentOutlet]="componentClass">` |
|
||||
| `*ngComponentOutlet="componentClass; inputs: myInputs";` | `<ng-template [ngComponentOutlet]="componentClass" [ngComponentOutletInputs]="myInputs">` |
|
||||
| `*myDir="exp as value"` | `<ng-template [myDir]="exp" let-value="myDir">` |
|
||||
| `*ngComponentOutlet="componentClass";` | `<ng-template [ngComponentOutlet]="componentClass">` |
|
||||
| `*ngComponentOutlet="componentClass; inputs: myInputs";` | `<ng-template [ngComponentOutlet]="componentClass" [ngComponentOutletInputs]="myInputs">` |
|
||||
| `*myDir="exp as value"` | `<ng-template [myDir]="exp" let-value="myDir">` |
|
||||
|
||||
## Improving template type checking for custom directives
|
||||
|
||||
|
|
@ -178,11 +178,11 @@ You can improve template type checking for custom directives by adding template
|
|||
These guards help the Angular template type checker find mistakes in the template at compile time, which can avoid runtime errors.
|
||||
Two different types of guards are possible:
|
||||
|
||||
* `ngTemplateGuard_(input)` lets you control how an input expression should be narrowed based on the type of a specific input.
|
||||
* `ngTemplateContextGuard` is used to determine the type of the context object for the template, based on the type of the directive itself.
|
||||
- `ngTemplateGuard_(input)` lets you control how an input expression should be narrowed based on the type of a specific input.
|
||||
- `ngTemplateContextGuard` is used to determine the type of the context object for the template, based on the type of the directive itself.
|
||||
|
||||
This section provides examples of both kinds of guards.
|
||||
For more information, see [Template type checking](tools/cli/template-typecheck "Template type-checking guide").
|
||||
For more information, see [Template type checking](tools/cli/template-typecheck 'Template type-checking guide').
|
||||
|
||||
### Type narrowing with template guards
|
||||
|
||||
|
|
@ -190,8 +190,8 @@ A structural directive in a template controls whether that template is rendered
|
|||
|
||||
There are two narrowings which are possible with input guards:
|
||||
|
||||
* Narrowing the input expression based on a TypeScript type assertion function.
|
||||
* Narrowing the input expression based on its truthiness.
|
||||
- Narrowing the input expression based on a TypeScript type assertion function.
|
||||
- Narrowing the input expression based on its truthiness.
|
||||
|
||||
To narrow the input expression by defining a type assertion function:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
# Drag and drop
|
||||
|
||||
## Overview
|
||||
|
||||
This page describes the drag and drop directives which lets you quickly create drag and drop interfaces with the following:
|
||||
|
||||
- Free dragging
|
||||
- Create a list of reorderable draggable elements
|
||||
- Transfer draggable elements between lists
|
||||
|
|
@ -32,9 +34,9 @@ import {Component} from '@angular/core';
|
|||
import {CdkDrag} from '@angular/cdk/drag-drop';
|
||||
|
||||
@Component({
|
||||
selector: 'my-custom-component',
|
||||
templateUrl: 'my-custom-component.html',
|
||||
imports: [CdkDrag],
|
||||
selector: 'my-custom-component',
|
||||
templateUrl: 'my-custom-component.html',
|
||||
imports: [CdkDrag],
|
||||
})
|
||||
export class DragDropExample {}
|
||||
</docs-code>
|
||||
|
|
@ -49,7 +51,6 @@ You can make any element draggable by adding the `cdkDrag` directive. By default
|
|||
<docs-code header="app/app.component.css" path="adev/src/content/examples/drag-drop/src/overview/app/app.component.css"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
|
||||
## Create a list of reorderable draggable elements
|
||||
|
||||
Add the `cdkDropList` directive to a parent element to group draggable elements into a reorderable collection. This defines where draggable elements can be dropped. The draggable elements in the drop list group rearrange automatically as an element moves.
|
||||
|
|
@ -67,6 +68,7 @@ You can use the `CDK_DROP_LIST` injection token that can be used to reference in
|
|||
## Transfer draggable elements between lists
|
||||
|
||||
The `cdkDropList` directive supports transferring draggable elements between connected drop lists. There are two ways to connect one or more `cdkDropList` instances together:
|
||||
|
||||
- Set the `cdkDropListConnectedTo` property to another drop list.
|
||||
- Wrap the elements in an element with the `cdkDropListGroup` attribute.
|
||||
|
||||
|
|
@ -143,7 +145,6 @@ By default, the user can drag the entire `cdkDrag` element to move it around. To
|
|||
<docs-code header="app/app.component.css" path="adev/src/content/examples/drag-drop/src/custom-handle/app/app.component.css"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
|
||||
You can use the `CDK_DRAG_HANDLE` injection token that can be used to reference instances of `cdkDragHandle`. For more information see the [dependency injection guide](https://angular.dev/guide/di) and the [drag handle injection token API](api/cdk/drag-drop/CDK_DRAG_HANDLE).
|
||||
|
||||
### Customize drag preview
|
||||
|
|
@ -168,11 +169,11 @@ By default, Angular inserts the `cdkDrag` preview into the `<body>` of the page
|
|||
|
||||
You can change where Angular inserts the preview using the `cdkDragPreviewContainer` input on `cdkDrag`. The possible values are:
|
||||
|
||||
| Value | Description | Advantages | Disadvantages |
|
||||
|:--- |:--- |:--- |:--- |
|
||||
| `global` | Default value. Angular inserts the preview into the <body> or the closest shadow root. | Preview won't be affected by `z-index` or `overflow: hidden`. It also won't affect `:nth-child` selectors and flex layouts. | Doesn't retain inherited styles. |
|
||||
| `parent` | Angular inserts the preview inside the parent of the element that is being dragged. | Preview inherits the same styles as the dragged element. | Preview may be clipped by `overflow: hidden` or be placed under other elements due to `z-index`. Furthermore, it can affect `:nth-child` selectors and some flex layouts. |
|
||||
| `ElementRef` or `HTMLElement` | Angular inserts the preview into the specified element. | Preview inherits styles from the specified container element. | Preview may be clipped by `overflow: hidden` or be placed under other elements due to `z-index`. Furthermore, it can affect `:nth-child` selectors and some flex layouts. |
|
||||
| Value | Description | Advantages | Disadvantages |
|
||||
| :---------------------------- | :------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `global` | Default value. Angular inserts the preview into the <body> or the closest shadow root. | Preview won't be affected by `z-index` or `overflow: hidden`. It also won't affect `:nth-child` selectors and flex layouts. | Doesn't retain inherited styles. |
|
||||
| `parent` | Angular inserts the preview inside the parent of the element that is being dragged. | Preview inherits the same styles as the dragged element. | Preview may be clipped by `overflow: hidden` or be placed under other elements due to `z-index`. Furthermore, it can affect `:nth-child` selectors and some flex layouts. |
|
||||
| `ElementRef` or `HTMLElement` | Angular inserts the preview into the specified element. | Preview inherits styles from the specified container element. | Preview may be clipped by `overflow: hidden` or be placed under other elements due to `z-index`. Furthermore, it can affect `:nth-child` selectors and some flex layouts. |
|
||||
|
||||
Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `previewContainer` within the config if the value is `global` or `parent`. For more information see the [dependency injection guide](https://angular.dev/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).
|
||||
|
||||
|
|
@ -260,7 +261,7 @@ If you want to disable dragging for a particular drag item, set the `cdkDragDisa
|
|||
<docs-code header="app/app.component.css" path="adev/src/content/examples/drag-drop/src/disable-drag/app/app.component.css"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `draggingDisabled` within the config. For more information see the [dependency injection guide](https://angular.dev/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).
|
||||
Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `draggingDisabled` within the config. For more information see the [dependency injection guide](https://angular.dev/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).
|
||||
|
||||
## Sorting customizations
|
||||
|
||||
|
|
@ -298,7 +299,6 @@ By default, `cdkDrag` elements are sorted into any position inside of a `cdkDrop
|
|||
<docs-code header="app/app.component.css" path="adev/src/content/examples/drag-drop/src/sort-predicate/app/app.component.css"/>
|
||||
</docs-code-multifile>
|
||||
|
||||
|
||||
### Disable sorting
|
||||
|
||||
There are cases where draggable elements can be dragged out of one `cdkDropList` into another, however the user shouldn't be able to sort them within the source list. For these cases, add the `cdkDropListSortingDisabled` attribute to prevent the draggable elements in a `cdkDropList` from sorting. This preserves the dragged element's initial position in the source list if it does not get dragged to a new valid position.
|
||||
|
|
@ -328,33 +328,33 @@ Combining `cdkDropListHasAnchor` with `cdkDropListSortingDisabled` makes it poss
|
|||
## Customize animations
|
||||
|
||||
Drag and drop supports animations for both:
|
||||
|
||||
- Sorting an draggable element inside a list
|
||||
- Moving the draggable element from the position that the user dropped it to the final position inside the list
|
||||
|
||||
To set up your animations, define a CSS transition that targets the transform property. The following classes can be used for animations:
|
||||
|
||||
| CSS class name | Result of adding transition |
|
||||
|:--- |:--- |
|
||||
| .cdk-drag | Animate draggable elements as they are being sorted. |
|
||||
| .cdk-drag-animating | Animate the draggable element from its dropped position to the final position within the `cdkDropList`.<br><br>This CSS class is applied to a `cdkDrag` element only when the dragging action has stopped. |
|
||||
| CSS class name | Result of adding transition |
|
||||
| :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| .cdk-drag | Animate draggable elements as they are being sorted. |
|
||||
| .cdk-drag-animating | Animate the draggable element from its dropped position to the final position within the `cdkDropList`.<br><br>This CSS class is applied to a `cdkDrag` element only when the dragging action has stopped. |
|
||||
|
||||
## Styling
|
||||
|
||||
Both `cdkDrag` and `cdkDropList` directives only apply essential styles needed for functionality. Applications can customize their styles by targeting these specified CSS classes.
|
||||
|
||||
|
||||
| CSS class name | Description |
|
||||
|:--- |:--- |
|
||||
| .cdk-drop-list | Selector for the `cdkDropList` container elements. |
|
||||
| .cdk-drag | Selector for `cdkDrag` elements. |
|
||||
| .cdk-drag-disabled | Selector for disabled `cdkDrag` elements. |
|
||||
| .cdk-drag-handle | Selector for the host element of the `cdkDragHandle`. |
|
||||
| .cdk-drag-preview | Selector for the drag preview element. This is the element that appears next to the cursor as a user drags an element in a sortable list.<br><br>The element looks exactly like the element that is being dragged unless customized with a custom template through `*cdkDragPreview`. |
|
||||
| .cdk-drag-placeholder | Selector for the drag placeholder element. This is the element that is shown in the spot where the draggable element will be dragged to once the dragging action ends.<br><br>This element looks exactly like the element that is being sorted unless customized with the cdkDragPlaceholder directive. |
|
||||
| .cdk-drop-list-dragging | Selector for `cdkDropList` container element that has a draggable element currently being dragged. |
|
||||
| .cdk-drop-list-disabled | Selector for `cdkDropList` container elements that are disabled. |
|
||||
| .cdk-drop-list-receiving | Selector for `cdkDropList` container element that has a draggable element it can receive from a connected drop list that is currently being dragged. |
|
||||
| .cdk-drag-anchor | Selector for the anchor element that is created when `cdkDropListHasAnchor` is enabled. This element indicates the position from which the dragged item started. |
|
||||
| CSS class name | Description |
|
||||
| :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| .cdk-drop-list | Selector for the `cdkDropList` container elements. |
|
||||
| .cdk-drag | Selector for `cdkDrag` elements. |
|
||||
| .cdk-drag-disabled | Selector for disabled `cdkDrag` elements. |
|
||||
| .cdk-drag-handle | Selector for the host element of the `cdkDragHandle`. |
|
||||
| .cdk-drag-preview | Selector for the drag preview element. This is the element that appears next to the cursor as a user drags an element in a sortable list.<br><br>The element looks exactly like the element that is being dragged unless customized with a custom template through `*cdkDragPreview`. |
|
||||
| .cdk-drag-placeholder | Selector for the drag placeholder element. This is the element that is shown in the spot where the draggable element will be dragged to once the dragging action ends.<br><br>This element looks exactly like the element that is being sorted unless customized with the cdkDragPlaceholder directive. |
|
||||
| .cdk-drop-list-dragging | Selector for `cdkDropList` container element that has a draggable element currently being dragged. |
|
||||
| .cdk-drop-list-disabled | Selector for `cdkDropList` container elements that are disabled. |
|
||||
| .cdk-drop-list-receiving | Selector for `cdkDropList` container element that has a draggable element it can receive from a connected drop list that is currently being dragged. |
|
||||
| .cdk-drag-anchor | Selector for the anchor element that is created when `cdkDropListHasAnchor` is enabled. This element indicates the position from which the dragged item started. |
|
||||
|
||||
## Dragging in a scrollable container
|
||||
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ Using an Angular custom element makes the process simpler and more transparent,
|
|||
The following Popup Service example application defines a component that you can either load dynamically or convert to a custom element.
|
||||
|
||||
| Files | Details |
|
||||
| :------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| :------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
|
||||
| `popup.component.ts` | Defines a simple pop-up element that displays an input message, with some animation and styling. |
|
||||
| `popup.service.ts` | Creates an injectable service that provides two different ways to invoke the `PopupComponent`; as a dynamic component, or as a custom element. Notice how much more setup is required for the dynamic-loading method. | |
|
||||
| `popup.service.ts` | Creates an injectable service that provides two different ways to invoke the `PopupComponent`; as a dynamic component, or as a custom element. Notice how much more setup is required for the dynamic-loading method. | |
|
||||
| `app.component.ts` | Defines the application's root component, which uses the `PopupService` to add the pop-up to the DOM at run time. When the application runs, the root component's constructor converts `PopupComponent` to a custom element. |
|
||||
|
||||
For comparison, the demo shows both methods.
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ The dynamic form itself is represented by a container component, which you add i
|
|||
Each question is represented in the form component's template by an `<app-question>` tag, which matches an instance of `DynamicFormQuestionComponent`.
|
||||
|
||||
The `DynamicFormQuestionComponent` is responsible for rendering the details of an individual question based on values in the data-bound question object.
|
||||
The form relies on a [`[formGroup]` directive](api/forms/FormGroupDirective "API reference") to connect the template HTML to the underlying control objects.
|
||||
The form relies on a [`[formGroup]` directive](api/forms/FormGroupDirective 'API reference') to connect the template HTML to the underlying control objects.
|
||||
The `DynamicFormQuestionComponent` creates form groups and populates them with controls defined in the question model, specifying display and validation rules.
|
||||
|
||||
<docs-code-multifile>
|
||||
|
|
@ -87,7 +87,7 @@ The `DynamicFormQuestionComponent` creates form groups and populates them with c
|
|||
The goal of the `DynamicFormQuestionComponent` is to present question types defined in your model.
|
||||
You only have two types of questions at this point but you can imagine many more.
|
||||
The `@switch` block in the template determines which type of question to display.
|
||||
The switch uses directives with the [`formControlName`](api/forms/FormControlName "FormControlName directive API reference") and [`formGroup`](api/forms/FormGroupDirective "FormGroupDirective API reference") selectors.
|
||||
The switch uses directives with the [`formControlName`](api/forms/FormControlName 'FormControlName directive API reference') and [`formGroup`](api/forms/FormGroupDirective 'FormGroupDirective API reference') selectors.
|
||||
Both directives are defined in `ReactiveFormsModule`.
|
||||
|
||||
### Supply data
|
||||
|
|
|
|||
|
|
@ -17,23 +17,22 @@ The following example exports `NgModel` into a variable called `name`:
|
|||
|
||||
Notice the following features illustrated by the example.
|
||||
|
||||
* The `<input>` element carries the HTML validation attributes: `required` and `minlength`.
|
||||
It also carries a custom validator directive, `forbiddenName`.
|
||||
For more information, see the [Custom validators](#defining-custom-validators) section.
|
||||
- The `<input>` element carries the HTML validation attributes: `required` and `minlength`.
|
||||
It also carries a custom validator directive, `forbiddenName`.
|
||||
For more information, see the [Custom validators](#defining-custom-validators) section.
|
||||
|
||||
* `#name="ngModel"` exports `NgModel` into a local variable called `name`.
|
||||
`NgModel` mirrors many of the properties of its underlying `FormControl` instance, so you can use this in the template to check for control states such as `valid` and `dirty`.
|
||||
For a full list of control properties, see the [AbstractControl](api/forms/AbstractControl) API reference.
|
||||
- `#name="ngModel"` exports `NgModel` into a local variable called `name`.
|
||||
`NgModel` mirrors many of the properties of its underlying `FormControl` instance, so you can use this in the template to check for control states such as `valid` and `dirty`.
|
||||
For a full list of control properties, see the [AbstractControl](api/forms/AbstractControl) API reference.
|
||||
- The outermost `@if` reveals a set of nested messages but only if the `name` is invalid and the control is either `dirty` or `touched`.
|
||||
|
||||
* The outermost `@if` reveals a set of nested messages but only if the `name` is invalid and the control is either `dirty` or `touched`.
|
||||
|
||||
* Each nested `@if` can present a custom message for one of the possible validation errors.
|
||||
There are messages for `required`, `minlength`, and `forbiddenName`.
|
||||
- Each nested `@if` can present a custom message for one of the possible validation errors.
|
||||
There are messages for `required`, `minlength`, and `forbiddenName`.
|
||||
|
||||
HELPFUL: To prevent the validator from displaying errors before the user has a chance to edit the form, you should check for either the `dirty` or `touched` states in a control.
|
||||
|
||||
* When the user changes the value in the watched field, the control is marked as "dirty"
|
||||
* When the user blurs the form control element, the control is marked as "touched"
|
||||
- When the user changes the value in the watched field, the control is marked as "dirty"
|
||||
- When the user blurs the form control element, the control is marked as "touched"
|
||||
|
||||
## Validating input in reactive forms
|
||||
|
||||
|
|
@ -45,8 +44,8 @@ Angular then calls these functions whenever the value of the control changes.
|
|||
|
||||
Validator functions can be either synchronous or asynchronous.
|
||||
|
||||
| Validator type | Details |
|
||||
|:--- |:--- |
|
||||
| Validator type | Details |
|
||||
| :--------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Sync validators | Synchronous functions that take a control instance and immediately return either a set of validation errors or `null`. Pass these in as the second argument when you instantiate a `FormControl`. |
|
||||
| Async validators | Asynchronous functions that take a control instance and return a Promise or Observable that later emits a set of validation errors or `null`. Pass these in as the third argument when you instantiate a `FormControl`. |
|
||||
|
||||
|
|
@ -77,7 +76,7 @@ If you look at the template for the `name` input again, it is fairly similar to
|
|||
|
||||
<docs-code header="reactive/actor-form-reactive.component.html (name with error msg)" path="adev/src/content/examples/form-validation/src/app/reactive/actor-form-reactive.component.html" visibleRegion="name-with-error-msg"/>
|
||||
|
||||
This form differs from the template-driven version in that it no longer exports any directives. Instead, it uses the `name` getter defined in the component class.
|
||||
This form differs from the template-driven version in that it no longer exports any directives. Instead, it uses the `name` getter defined in the component class.
|
||||
|
||||
Notice that the `required` attribute is still present in the template. Although it's not necessary for validation, it should be retained for accessibility purposes.
|
||||
|
||||
|
|
@ -90,13 +89,13 @@ Here's what the definition of that function looks like.
|
|||
|
||||
<docs-code header="shared/forbidden-name.directive.ts (forbiddenNameValidator)" path="adev/src/content/examples/form-validation/src/app/shared/forbidden-name.directive.ts" visibleRegion="custom-validator"/>
|
||||
|
||||
The function is a factory that takes a regular expression to detect a *specific* forbidden name and returns a validator function.
|
||||
The function is a factory that takes a regular expression to detect a _specific_ forbidden name and returns a validator function.
|
||||
|
||||
In this sample, the forbidden name is "bob", so the validator rejects any actor name containing "bob".
|
||||
Elsewhere it could reject "alice" or any name that the configuring regular expression matches.
|
||||
|
||||
The `forbiddenNameValidator` factory returns the configured validator function.
|
||||
That function takes an Angular control object and returns *either* null if the control value is valid *or* a validation error object.
|
||||
That function takes an Angular control object and returns _either_ null if the control value is valid _or_ a validation error object.
|
||||
The validation error object typically has a property whose name is the validation key, `'forbiddenName'`, and whose value is an arbitrary dictionary of values that you could insert into an error message, `{name}`.
|
||||
|
||||
Custom async validators are similar to sync validators, but they must instead return a Promise or observable that later emits null or a validation error object.
|
||||
|
|
@ -129,7 +128,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` —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`.
|
||||
|
||||
|
|
@ -139,14 +138,14 @@ Angular automatically mirrors many control properties onto the form control elem
|
|||
Use these classes to style form control elements according to the state of the form.
|
||||
The following classes are currently supported.
|
||||
|
||||
* `.ng-valid`
|
||||
* `.ng-invalid`
|
||||
* `.ng-pending`
|
||||
* `.ng-pristine`
|
||||
* `.ng-dirty`
|
||||
* `.ng-untouched`
|
||||
* `.ng-touched`
|
||||
* `.ng-submitted` \(enclosing form element only\)
|
||||
- `.ng-valid`
|
||||
- `.ng-invalid`
|
||||
- `.ng-pending`
|
||||
- `.ng-pristine`
|
||||
- `.ng-dirty`
|
||||
- `.ng-untouched`
|
||||
- `.ng-touched`
|
||||
- `.ng-submitted` \(enclosing form element only\)
|
||||
|
||||
In the following example, the actor form uses the `.ng-valid` and `.ng-invalid` classes to
|
||||
set the color of each form control's border.
|
||||
|
|
@ -155,14 +154,14 @@ set the color of each form control's border.
|
|||
|
||||
## Cross-field validation
|
||||
|
||||
A cross-field validator is a [custom validator](#defining-custom-validators "Read about custom validators") that compares the values of different fields in a form and accepts or rejects them in combination.
|
||||
A cross-field validator is a [custom validator](#defining-custom-validators 'Read about custom validators') that compares the values of different fields in a form and accepts or rejects them in combination.
|
||||
For example, you might have a form that offers mutually incompatible options, so that if the user can choose A or B, but not both.
|
||||
Some field values might also depend on others; a user might be allowed to choose B only if A is also chosen.
|
||||
|
||||
The following cross validation examples show how to do the following:
|
||||
|
||||
* Validate reactive or template-based form input based on the values of two sibling controls,
|
||||
* Show a descriptive error message after the user interacted with the form and the validation failed.
|
||||
- Validate reactive or template-based form input based on the values of two sibling controls,
|
||||
- Show a descriptive error message after the user interacted with the form and the validation failed.
|
||||
|
||||
The examples use cross-validation to ensure that actors do not reuse the same name in their role by filling out the Actor Form.
|
||||
The validators do this by checking that the actor names and roles do not match.
|
||||
|
|
@ -174,9 +173,9 @@ The form has the following structure:
|
|||
<docs-code language="javascript">
|
||||
|
||||
const actorForm = new FormGroup({
|
||||
'name': new FormControl(),
|
||||
'role': new FormControl(),
|
||||
'skill': new FormControl()
|
||||
'name': new FormControl(),
|
||||
'role': new FormControl(),
|
||||
'skill': new FormControl()
|
||||
});
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -190,9 +189,9 @@ To add a validator to the `FormGroup`, pass the new validator in as the second a
|
|||
<docs-code language="javascript">
|
||||
|
||||
const actorForm = new FormGroup({
|
||||
'name': new FormControl(),
|
||||
'role': new FormControl(),
|
||||
'skill': new FormControl()
|
||||
'name': new FormControl(),
|
||||
'role': new FormControl(),
|
||||
'skill': new FormControl()
|
||||
}, { validators: unambiguousRoleValidator });
|
||||
|
||||
</docs-code>
|
||||
|
|
@ -238,9 +237,9 @@ This is the same in both template-driven and reactive forms.
|
|||
Asynchronous validators implement the `AsyncValidatorFn` and `AsyncValidator` interfaces.
|
||||
These are very similar to their synchronous counterparts, with the following differences.
|
||||
|
||||
* The `validate()` functions must return a Promise or an observable,
|
||||
* The observable returned must be finite, meaning it must complete at some point.
|
||||
To convert an infinite observable into a finite one, pipe the observable through a filtering operator such as `first`, `last`, `take`, or `takeUntil`.
|
||||
- The `validate()` functions must return a Promise or an observable,
|
||||
- The observable returned must be finite, meaning it must complete at some point.
|
||||
To convert an infinite observable into a finite one, pipe the observable through a filtering operator such as `first`, `last`, `take`, or `takeUntil`.
|
||||
|
||||
Asynchronous validation happens after the synchronous validation, and is performed only if the synchronous validation is successful.
|
||||
This check lets forms avoid potentially expensive async validation processes \(such as an HTTP request\) if the more basic validation methods have already found invalid input.
|
||||
|
|
@ -255,7 +254,7 @@ The following example shows how to achieve this in a template-driven form.
|
|||
|
||||
<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator>
|
||||
@if(model.pending) {
|
||||
<app-spinner />
|
||||
<app-spinner />
|
||||
}
|
||||
</docs-code>
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ In the course of this tutorial, you bind a sample form to data and handle user i
|
|||
The initial template defines the layout for a form with two form groups and a submit button.
|
||||
The form groups correspond to two properties of the Actor data model, name and studio.
|
||||
Each group has a label and a box for user input.
|
||||
|
||||
- The **Name** `<input>` control element has the HTML5 `required` attribute
|
||||
- The **Studio** `<input>` control element does not because `studio` is optional
|
||||
|
||||
|
|
@ -204,7 +203,6 @@ To see how the classes are added and removed by the framework, open the browser'
|
|||
</docs-code>
|
||||
|
||||
1. Take the following actions on the **Name** `<input>` box, and observe which classes appear.
|
||||
|
||||
- Look but don't touch.
|
||||
The classes indicate that it is untouched, pristine, and valid.
|
||||
|
||||
|
|
|
|||
|
|
@ -76,18 +76,19 @@ bootstrapApplication(App, {
|
|||
```
|
||||
|
||||
### How event replay works
|
||||
|
||||
Event Replay is a feature that improves user experience by capturing user events that were triggered before the hydration process is complete. Then those events are replayed, ensuring none of that interaction was lost.
|
||||
|
||||
The Event Replay is divided into three main phases:
|
||||
|
||||
- **Capturing user interactions**<br>
|
||||
Prior to **Hydration**, Event Replay captures and stores all interactions that the user may perform, such as clicks and other browser native events.
|
||||
Prior to **Hydration**, Event Replay captures and stores all interactions that the user may perform, such as clicks and other browser native events.
|
||||
|
||||
- **Storing events**<br>
|
||||
The **Event Contract** keeps in memory all the interactions recorded in the previous step, ensuring that they are not lost for later replay.
|
||||
The **Event Contract** keeps in memory all the interactions recorded in the previous step, ensuring that they are not lost for later replay.
|
||||
|
||||
- **Relaunch of events**<br>
|
||||
Once **Hydration** is complete, Angular re-invokes the captured events.
|
||||
Once **Hydration** is complete, Angular re-invokes the captured events.
|
||||
|
||||
Event replay supports _native browser events_, for example `click`, `mouseover`, and `focusin`. If you'd like to learn more about JSAction, the library that powers event replay, you can read more [on the readme](https://github.com/angular/angular/tree/main/packages/core/primitives/event-dispatch#readme).
|
||||
|
||||
|
|
@ -195,6 +196,7 @@ bootstrapApplication(AppComponent, {
|
|||
```
|
||||
|
||||
## Consistent rendering across server-side and client-side
|
||||
|
||||
Avoid introducing `@if` blocks and other conditionals that display different content when server-side rendering than client-side rendering, such as using an `@if` block with Angular's `isPlatformBrowser` function. These rendering differences cause layout shifts, negatively impacting end-user experience and core web vitals.
|
||||
|
||||
## Third Party Libraries with DOM Manipulation
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ If `@angular/localize` is not installed and you try to build a localized version
|
|||
|
||||
## Options
|
||||
|
||||
| OPTION | DESCRIPTION | VALUE TYPE | DEFAULT VALUE
|
||||
|:--- |:--- |:------ |:------
|
||||
| `--project` | The name of the project. | `string` |
|
||||
| `--use-at-runtime` | If set, then `$localize` can be used at runtime. Also `@angular/localize` gets included in the `dependencies` section of `package.json`, rather than `devDependencies`, which is the default. | `boolean` | `false`
|
||||
| OPTION | DESCRIPTION | VALUE TYPE | DEFAULT VALUE |
|
||||
| :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------- | :------------ |
|
||||
| `--project` | The name of the project. | `string` |
|
||||
| `--use-at-runtime` | If set, then `$localize` can be used at runtime. Also `@angular/localize` gets included in the `dependencies` section of `package.json`, rather than `devDependencies`, which is the default. | `boolean` | `false` |
|
||||
|
||||
For more available options, see `ng add` in [Angular CLI][CliMain].
|
||||
|
||||
|
|
@ -28,8 +28,6 @@ For more available options, see `ng add` in [Angular CLI][CliMain].
|
|||
<docs-pill href="guide/i18n/locale-id" title="Refer to locales by ID"/>
|
||||
</docs-pill-row>
|
||||
|
||||
[CliMain]: cli "CLI Overview and Command Reference | Angular"
|
||||
|
||||
[GuideNpmPackages]: reference/configs/npm-packages "Workspace npm dependencies | Angular"
|
||||
|
||||
[GuideTsConfig]: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html "TypeScript Configuration"
|
||||
[CliMain]: cli 'CLI Overview and Command Reference | Angular'
|
||||
[GuideNpmPackages]: reference/configs/npm-packages 'Workspace npm dependencies | Angular'
|
||||
[GuideTsConfig]: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html 'TypeScript Configuration'
|
||||
|
|
|
|||
|
|
@ -35,8 +35,6 @@ The following example displays an Apache configuration.
|
|||
|
||||
<docs-code path="adev/src/content/examples/i18n/doc-files/apache2.conf" language="apache"/>
|
||||
|
||||
[CliBuild]: cli/build "ng build | CLI | Angular"
|
||||
|
||||
[GuideDeployment]: tools/cli/deployment "Deployment | Angular"
|
||||
|
||||
[GuideWorkspaceConfig]: reference/configs/workspace-config "Angular workspace configuration | Angular"
|
||||
[CliBuild]: cli/build 'ng build | CLI | Angular'
|
||||
[GuideDeployment]: tools/cli/deployment 'Deployment | Angular'
|
||||
[GuideWorkspaceConfig]: reference/configs/workspace-config 'Angular workspace configuration | Angular'
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
Angular provides the following built-in data transformation [pipes](guide/templates/pipes).
|
||||
The data transformation pipes use the [`LOCALE_ID`][ApiCoreLocaleId] token to format data based on rules of each locale.
|
||||
|
||||
| Data transformation pipe | Details |
|
||||
|:--- |:--- |
|
||||
| Data transformation pipe | Details |
|
||||
| :-------------------------------------- | :------------------------------------------------ |
|
||||
| [`DatePipe`][ApiCommonDatepipe] | Formats a date value. |
|
||||
| [`CurrencyPipe`][ApiCommonCurrencypipe] | Transforms a number into a currency string. |
|
||||
| [`DecimalPipe`][ApiCommonDecimalpipe] | Transforms a number into a decimal number string. |
|
||||
|
|
@ -44,9 +44,8 @@ HELPFUL: The locale specified for the `CurrencyPipe` overrides the global `LOCAL
|
|||
<docs-pill href="guide/i18n/prepare" title="Prepare component for translation"/>
|
||||
</docs-pill-row>
|
||||
|
||||
[ApiCommonCurrencypipe]: api/common/CurrencyPipe "CurrencyPipe | Common - API | Angular"
|
||||
|
||||
[ApiCommonDatepipe]: api/common/DatePipe "DatePipe | Common - API | Angular"
|
||||
[ApiCommonDecimalpipe]: api/common/DecimalPipe "DecimalPipe | Common - API | Angular"
|
||||
[ApiCommonPercentpipe]: api/common/PercentPipe "PercentPipe | Common - API | Angular"
|
||||
[ApiCoreLocaleId]: api/core/LOCALE_ID "LOCALE_ID | Core - API | Angular"
|
||||
[ApiCommonCurrencypipe]: api/common/CurrencyPipe 'CurrencyPipe | Common - API | Angular'
|
||||
[ApiCommonDatepipe]: api/common/DatePipe 'DatePipe | Common - API | Angular'
|
||||
[ApiCommonDecimalpipe]: api/common/DecimalPipe 'DecimalPipe | Common - API | Angular'
|
||||
[ApiCommonPercentpipe]: api/common/PercentPipe 'PercentPipe | Common - API | Angular'
|
||||
[ApiCoreLocaleId]: api/core/LOCALE_ID 'LOCALE_ID | Core - API | Angular'
|
||||
|
|
|
|||
|
|
@ -24,5 +24,5 @@ For example, you could import the global variants for French \(`fr`\) in `main.t
|
|||
|
||||
HELPFUL: In an `NgModules` application, you would import it in your `app.module`.
|
||||
|
||||
[CliMain]: cli "CLI Overview and Command Reference | Angular"
|
||||
[CliBuild]: cli/build "ng build | CLI | Angular"
|
||||
[CliMain]: cli 'CLI Overview and Command Reference | Angular'
|
||||
[CliBuild]: cli/build 'ng build | CLI | Angular'
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
# Refer to locales by ID
|
||||
|
||||
Angular uses the Unicode *locale identifier* \(Unicode locale ID\) to find the correct locale data for internationalization of text strings.
|
||||
Angular uses the Unicode _locale identifier_ \(Unicode locale ID\) to find the correct locale data for internationalization of text strings.
|
||||
|
||||
<docs-callout title="Unicode locale ID">
|
||||
|
||||
* A locale ID conforms to the [Unicode Common Locale Data Repository (CLDR) core specification][UnicodeCldrDevelopmentCoreSpecification].
|
||||
For more information about locale IDs, see [Unicode Language and Locale Identifiers][UnicodeCldrDevelopmentCoreSpecificationLocaleIDs].
|
||||
- A locale ID conforms to the [Unicode Common Locale Data Repository (CLDR) core specification][UnicodeCldrDevelopmentCoreSpecification].
|
||||
For more information about locale IDs, see [Unicode Language and Locale Identifiers][UnicodeCldrDevelopmentCoreSpecificationLocaleIDs].
|
||||
|
||||
* CLDR and Angular use [BCP 47 tags][RfcEditorInfoBcp47] as the base for the locale ID
|
||||
- CLDR and Angular use [BCP 47 tags][RfcEditorInfoBcp47] as the base for the locale ID
|
||||
|
||||
</docs-callout>
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ The differences include grammar, punctuation, formats for currency, decimal numb
|
|||
For the examples in this guide, use the following languages and locales.
|
||||
|
||||
| Language | Locale | Unicode locale ID |
|
||||
|:--- |:--- |:--- |
|
||||
| :------- | :----------------------- | :---------------- |
|
||||
| English | Canada | `en-CA` |
|
||||
| English | United States of America | `en-US` |
|
||||
| French | Canada | `fr-CA` |
|
||||
|
|
@ -48,6 +48,7 @@ To change the source locale of your project for the build, complete the followin
|
|||
|
||||
1. Open the [`angular.json`][GuideWorkspaceConfig] workspace build configuration file.
|
||||
2. Add or modify the `sourceLocale` field inside the `i18n` section:
|
||||
|
||||
```json
|
||||
{
|
||||
"projects": {
|
||||
|
|
@ -66,12 +67,8 @@ To change the source locale of your project for the build, complete the followin
|
|||
<docs-pill href="guide/i18n/format-data-locale" title="Format data based on locale"/>
|
||||
</docs-pill-row>
|
||||
|
||||
[GuideWorkspaceConfig]: reference/configs/workspace-config "Angular workspace configuration | Angular"
|
||||
|
||||
[GithubAngularAngularTreeMasterPackagesCommonLocales]: <https://github.com/angular/angular/tree/main/packages/common/locales> "angular/packages/common/locales | angular/angular | GitHub"
|
||||
|
||||
[RfcEditorInfoBcp47]: https://www.rfc-editor.org/info/bcp47 "BCP 47 | RFC Editor"
|
||||
|
||||
[UnicodeCldrDevelopmentCoreSpecification]: https://cldr.unicode.org/index/cldr-spec "Core Specification | Unicode CLDR Project"
|
||||
|
||||
[UnicodeCldrDevelopmentCoreSpecificationLocaleID]: https://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code "Unicode Language and Locale Identifiers - Core Specification | Unicode CLDR Project"
|
||||
[GuideWorkspaceConfig]: reference/configs/workspace-config 'Angular workspace configuration | Angular'
|
||||
[GithubAngularAngularTreeMasterPackagesCommonLocales]: https://github.com/angular/angular/tree/main/packages/common/locales 'angular/packages/common/locales | angular/angular | GitHub'
|
||||
[RfcEditorInfoBcp47]: https://www.rfc-editor.org/info/bcp47 'BCP 47 | RFC Editor'
|
||||
[UnicodeCldrDevelopmentCoreSpecification]: https://cldr.unicode.org/index/cldr-spec 'Core Specification | Unicode CLDR Project'
|
||||
[UnicodeCldrDevelopmentCoreSpecificationLocaleID]: https://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code 'Unicode Language and Locale Identifiers - Core Specification | Unicode CLDR Project'
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
The Angular extractor generates a file with a translation unit entry each of the following instances.
|
||||
|
||||
* Each `i18n` attribute in a component template
|
||||
* Each [`$localize`][ApiLocalizeInitLocalize] tagged message string in component code
|
||||
- Each `i18n` attribute in a component template
|
||||
- Each [`$localize`][ApiLocalizeInitLocalize] tagged message string in component code
|
||||
|
||||
As described in [How meanings control text extraction and merges][GuideI18nCommonPrepareHowMeaningsControlTextExtractionAndMerges], Angular assigns each translation unit a unique ID.
|
||||
|
||||
|
|
@ -93,6 +93,5 @@ Both elements now use the same translation \(`Bonjour`\), because both were defi
|
|||
|
||||
<docs-code path="adev/src/content/examples/i18n/doc-files/rendered-output.html"/>
|
||||
|
||||
[ApiLocalizeInitLocalize]: api/localize/init/$localize "$localize | init - localize - API | Angular"
|
||||
|
||||
[GuideI18nCommonPrepareHowMeaningsControlTextExtractionAndMerges]: guide/i18n/prepare#h1-example "How meanings control text extraction and merges - Prepare components for translations | Angular"
|
||||
[ApiLocalizeInitLocalize]: api/localize/init/$localize '$localize | init - localize - API | Angular'
|
||||
[GuideI18nCommonPrepareHowMeaningsControlTextExtractionAndMerges]: guide/i18n/prepare#h1-example 'How meanings control text extraction and merges - Prepare components for translations | Angular'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ To merge the completed translations into your project, complete the following ac
|
|||
|
||||
1. Use the [Angular CLI][CliMain] to build a copy of the distributable files of your project
|
||||
1. Use the `"localize"` option to replace all of the i18n messages with the valid translations and build a localized variant application.
|
||||
A variant application is a complete copy of the distributable files of your application translated for a single locale.
|
||||
A variant application is a complete copy of the distributable files of your application translated for a single locale.
|
||||
|
||||
After you merge the translations, serve each distributable copy of the application using server-side language detection or different subdirectories.
|
||||
|
||||
|
|
@ -31,8 +31,8 @@ Use the `i18n` project option in the [`angular.json`][GuideWorkspaceConfig] work
|
|||
|
||||
The following sub-options identify the source language and tell the compiler where to find supported translations for the project.
|
||||
|
||||
| Suboption | Details |
|
||||
|:--- |:--- |
|
||||
| Suboption | Details |
|
||||
| :------------- | :--------------------------------------------------------------------------- |
|
||||
| `sourceLocale` | The locale you use within the application source code \(`en-US` by default\) |
|
||||
| `locales` | A map of locale identifiers to translation files |
|
||||
|
||||
|
|
@ -46,9 +46,9 @@ For example, the following excerpt of an [`angular.json`][GuideWorkspaceConfig]
|
|||
|
||||
To use your locale definition in the build configuration, use the `"localize"` option in the [`angular.json`][GuideWorkspaceConfig] workspace build configuration file to tell the CLI which locales to generate for the build configuration.
|
||||
|
||||
* Set `"localize"` to `true` for all the locales previously defined in the build configuration.
|
||||
* Set `"localize"` to an array of a subset of the previously defined locale identifiers to build only those locale versions.
|
||||
* Set `"localize"` to `false` to disable localization and not generate any locale-specific versions.
|
||||
- Set `"localize"` to `true` for all the locales previously defined in the build configuration.
|
||||
- Set `"localize"` to an array of a subset of the previously defined locale identifiers to build only those locale versions.
|
||||
- Set `"localize"` to `false` to disable localization and not generate any locale-specific versions.
|
||||
|
||||
HELPFUL: Ahead-of-time (AOT) compilation is required to localize component templates.
|
||||
|
||||
|
|
@ -110,8 +110,8 @@ For production builds, use configuration composition to run both configurations.
|
|||
When a translation is missing, the build succeeds but generates a warning such as `Missing translation for message "{translation_text}"`.
|
||||
To configure the level of warning that is generated by the Angular compiler, specify one of the following levels.
|
||||
|
||||
| Warning level | Details | Output |
|
||||
|:--- |:--- |:--- |
|
||||
| Warning level | Details | Output |
|
||||
| :------------ | :--------------------------------------------------- | :----------------------------------------------------- |
|
||||
| `error` | Throw an error and the build fails | n/a |
|
||||
| `ignore` | Do nothing | n/a |
|
||||
| `warning` | Displays the default warning in the console or shell | `Missing translation for message "{translation_text}"` |
|
||||
|
|
@ -128,7 +128,7 @@ HELPFUL: When you compile your Angular project into an Angular application, the
|
|||
This means that your Angular application is translated after compilation.
|
||||
This also means that you can create localized versions of your Angular application without re-compiling your entire Angular project for each locale.
|
||||
|
||||
When you translate your Angular application, the *translation transformation* replaces and reorders the parts \(static strings and expressions\) of the template literal string with strings from a collection of translations.
|
||||
When you translate your Angular application, the _translation transformation_ replaces and reorders the parts \(static strings and expressions\) of the template literal string with strings from a collection of translations.
|
||||
For more information, see [`$localize`][ApiLocalizeInitLocalize].
|
||||
|
||||
TLDR: Compile once, then translate for each locale.
|
||||
|
|
@ -139,18 +139,13 @@ TLDR: Compile once, then translate for each locale.
|
|||
<docs-pill href="guide/i18n/deploy" title="Deploy multiple locales"/>
|
||||
</docs-pill-row>
|
||||
|
||||
[ApiLocalizeInitLocalize]: api/localize/init/$localize "$localize | init - localize - API | Angular"
|
||||
|
||||
[CliMain]: cli "CLI Overview and Command Reference | Angular"
|
||||
[CliBuild]: cli/build "ng build | CLI | Angular"
|
||||
|
||||
[GuideBuild]: tools/cli/build "Building and serving Angular apps | Angular"
|
||||
|
||||
[GuideI18nCommonMergeApplySpecificBuildOptionsForJustOneLocale]: guide/i18n/merge#apply-specific-build-options-for-just-one-locale "Apply specific build options for just one locale - Merge translations into the application | Angular"
|
||||
[GuideI18nCommonMergeBuildFromTheCommandLine]: guide/i18n/merge#build-from-the-command-line "Build from the command line - Merge translations into the application | Angular"
|
||||
[GuideI18nCommonMergeDefineLocalesInTheBuildConfiguration]: guide/i18n/merge#define-locales-in-the-build-configuration "Define locales in the build configuration - Merge translations into the application | Angular"
|
||||
[GuideI18nCommonMergeGenerateApplicationVariantsForEachLocale]: guide/i18n/merge#generate-application-variants-for-each-locale "Generate application variants for each locale - Merge translations into the application | Angular"
|
||||
|
||||
[GuideI18nCommonTranslationFilesChangeTheSourceLanguageFileFormat]: guide/i18n/translation-files#change-the-source-language-file-format "Change the source language file format - Work with translation files | Angular"
|
||||
|
||||
[GuideWorkspaceConfig]: reference/configs/workspace-config "Angular workspace configuration | Angular"
|
||||
[ApiLocalizeInitLocalize]: api/localize/init/$localize '$localize | init - localize - API | Angular'
|
||||
[CliMain]: cli 'CLI Overview and Command Reference | Angular'
|
||||
[CliBuild]: cli/build 'ng build | CLI | Angular'
|
||||
[GuideBuild]: tools/cli/build 'Building and serving Angular apps | Angular'
|
||||
[GuideI18nCommonMergeApplySpecificBuildOptionsForJustOneLocale]: guide/i18n/merge#apply-specific-build-options-for-just-one-locale 'Apply specific build options for just one locale - Merge translations into the application | Angular'
|
||||
[GuideI18nCommonMergeBuildFromTheCommandLine]: guide/i18n/merge#build-from-the-command-line 'Build from the command line - Merge translations into the application | Angular'
|
||||
[GuideI18nCommonMergeDefineLocalesInTheBuildConfiguration]: guide/i18n/merge#define-locales-in-the-build-configuration 'Define locales in the build configuration - Merge translations into the application | Angular'
|
||||
[GuideI18nCommonMergeGenerateApplicationVariantsForEachLocale]: guide/i18n/merge#generate-application-variants-for-each-locale 'Generate application variants for each locale - Merge translations into the application | Angular'
|
||||
[GuideI18nCommonTranslationFilesChangeTheSourceLanguageFileFormat]: guide/i18n/translation-files#change-the-source-language-file-format 'Change the source language file format - Work with translation files | Angular'
|
||||
[GuideWorkspaceConfig]: reference/configs/workspace-config 'Angular workspace configuration | Angular'
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
# Angular Internationalization (i18n)
|
||||
|
||||
*Internationalization*, sometimes referenced as i18n, is the process of designing and preparing your project for use in different locales around the world.
|
||||
*Localization* is the process of building versions of your project for different locales.
|
||||
_Internationalization_, sometimes referenced as i18n, is the process of designing and preparing your project for use in different locales around the world.
|
||||
_Localization_ is the process of building versions of your project for different locales.
|
||||
The localization process includes the following actions.
|
||||
|
||||
* Extract text for translation into different languages
|
||||
* Format data for a specific locale
|
||||
- Extract text for translation into different languages
|
||||
- Format data for a specific locale
|
||||
|
||||
A *locale* identifies a region in which people speak a particular language or language variant.
|
||||
A _locale_ identifies a region in which people speak a particular language or language variant.
|
||||
Possible regions include countries and geographical regions.
|
||||
A locale determines the formatting and parsing of the following details.
|
||||
|
||||
* Measurement units including date and time, numbers, and currencies
|
||||
* Translated names including time zones, languages, and countries
|
||||
- Measurement units including date and time, numbers, and currencies
|
||||
- Translated names including time zones, languages, and countries
|
||||
|
||||
For a quick introduction to localization and internationalization watch this video:
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ To mark the greeting for translation, add the `i18n` attribute to the `<h1>` tag
|
|||
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/i18n/doc-files/app.component.html" visibleRegion="i18n-attribute"/>
|
||||
|
||||
|
||||
### using conditional statement with `i18n`
|
||||
|
||||
The following `<div>` tag will display translated text as part of `div` and `aria-label` based on toggle status
|
||||
|
|
@ -125,8 +124,6 @@ $localize`string_to_translate ${variable_name}:placeholder_name:`;
|
|||
return this.show ? $localize`Show Tabs` : $localize`Hide tabs`;
|
||||
</docs-code>
|
||||
|
||||
|
||||
|
||||
## i18n metadata for translation
|
||||
|
||||
<!--todo: replace with docs-code -->
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
# Work with translation files
|
||||
|
||||
After you prepare a component for translation, use the [`extract-i18n`][CliExtractI18n] [Angular CLI][CliMain] command to extract the marked text in the component into a *source language* file.
|
||||
After you prepare a component for translation, use the [`extract-i18n`][CliExtractI18n] [Angular CLI][CliMain] command to extract the marked text in the component into a _source language_ file.
|
||||
|
||||
The marked text includes text marked with `i18n`, attributes marked with `i18n-`*attribute*, and text tagged with `$localize` as described in [Prepare component for translation][GuideI18nCommonPrepare].
|
||||
The marked text includes text marked with `i18n`, attributes marked with `i18n-`_attribute_, and text tagged with `$localize` as described in [Prepare component for translation][GuideI18nCommonPrepare].
|
||||
|
||||
Complete the following steps to create and update translation files for your project.
|
||||
|
||||
1. [Extract the source language file][GuideI18nCommonTranslationFilesExtractTheSourceLanguageFile].
|
||||
1. Optionally, change the location, format, and name.
|
||||
1. Optionally, change the location, format, and name.
|
||||
1. Copy the source language file to [create a translation file for each language][GuideI18nCommonTranslationFilesCreateATranslationFileForEachLanguage].
|
||||
1. [Translate each translation file][GuideI18nCommonTranslationFilesTranslateEachTranslationFile].
|
||||
1. Translate plurals and alternate expressions separately.
|
||||
1. [Translate plurals][GuideI18nCommonTranslationFilesTranslatePlurals].
|
||||
1. [Translate alternate expressions][GuideI18nCommonTranslationFilesTranslateAlternateExpressions].
|
||||
1. [Translate nested expressions][GuideI18nCommonTranslationFilesTranslateNestedExpressions].
|
||||
1. [Translate plurals][GuideI18nCommonTranslationFilesTranslatePlurals].
|
||||
1. [Translate alternate expressions][GuideI18nCommonTranslationFilesTranslateAlternateExpressions].
|
||||
1. [Translate nested expressions][GuideI18nCommonTranslationFilesTranslateNestedExpressions].
|
||||
|
||||
## Extract the source language file
|
||||
|
||||
|
|
@ -23,17 +23,17 @@ To extract the source language file, complete the following actions.
|
|||
1. Change to the root directory of your project.
|
||||
1. Run the following CLI command.
|
||||
|
||||
<docs-code path="adev/src/content/examples/i18n/doc-files/commands.sh" visibleRegion="extract-i18n-default"/>
|
||||
<docs-code path="adev/src/content/examples/i18n/doc-files/commands.sh" visibleRegion="extract-i18n-default"/>
|
||||
|
||||
The `extract-i18n` command creates a source language file named `messages.xlf` in the root directory of your project.
|
||||
For more information about the XML Localization Interchange File Format \(XLIFF, version 1.2\), see [XLIFF][WikipediaWikiXliff].
|
||||
|
||||
Use the following [`extract-i18n`][CliExtractI18n] command options to change the source language file location, format, and file name.
|
||||
|
||||
| Command option | Details |
|
||||
|:--- |:--- |
|
||||
| Command option | Details |
|
||||
| :-------------- | :----------------------------------- |
|
||||
| `--format` | Set the format of the output file |
|
||||
| `--out-file` | Set the name of the output file |
|
||||
| `--out-file` | Set the name of the output file |
|
||||
| `--output-path` | Set the path of the output directory |
|
||||
|
||||
### Change the source language file location
|
||||
|
|
@ -50,8 +50,8 @@ The following example specifies the output path as an option.
|
|||
|
||||
The `extract-i18n` command creates files in the following translation formats.
|
||||
|
||||
| Translation format | Details | File extension |
|
||||
|:--- |:--- |:--- |
|
||||
| Translation format | Details | File extension |
|
||||
| :----------------- | :--------------------------------------------------------------------------------------------------------------- | :---------------- |
|
||||
| ARB | [Application Resource Bundle][GithubGoogleAppResourceBundleWikiApplicationresourcebundlespecification] | `.arb` |
|
||||
| JSON | [JavaScript Object Notation][JsonMain] | `.json` |
|
||||
| XLIFF 1.2 | [XML Localization Interchange File Format, version 1.2][OasisOpenDocsXliffXliffCoreXliffCoreHtml] | `.xlf` |
|
||||
|
|
@ -83,25 +83,25 @@ The following example demonstrates naming the output file.
|
|||
To create a translation file for a locale or language, complete the following actions.
|
||||
|
||||
1. [Extract the source language file][GuideI18nCommonTranslationFilesExtractTheSourceLanguageFile].
|
||||
1. Make a copy of the source language file to create a *translation* file for each language.
|
||||
1. Rename the *translation* file to add the locale.
|
||||
1. Make a copy of the source language file to create a _translation_ file for each language.
|
||||
1. Rename the _translation_ file to add the locale.
|
||||
|
||||
<docs-code language="file">
|
||||
<docs-code language="file">
|
||||
|
||||
messages.xlf --> messages.{locale}.xlf
|
||||
messages.xlf --> messages.{locale}.xlf
|
||||
|
||||
</docs-code>
|
||||
</docs-code>
|
||||
|
||||
1. Create a new directory at your project root named `locale`.
|
||||
|
||||
<docs-code language="file">
|
||||
<docs-code language="file">
|
||||
|
||||
src/locale
|
||||
src/locale
|
||||
|
||||
</docs-code>
|
||||
</docs-code>
|
||||
|
||||
1. Move the *translation* file to the new directory.
|
||||
1. Send the *translation* file to your translator.
|
||||
1. Move the _translation_ file to the new directory.
|
||||
1. Send the _translation_ file to your translator.
|
||||
1. Repeat the above steps for each language you want to add to your application.
|
||||
|
||||
### `extract-i18n` example for French
|
||||
|
|
@ -120,32 +120,32 @@ Unless you are fluent in the language and have the time to edit translations, yo
|
|||
|
||||
1. Send each translation file to a translator.
|
||||
1. The translator uses an XLIFF file editor to complete the following actions.
|
||||
1. Create the translation.
|
||||
1. Edit the translation.
|
||||
1. Create the translation.
|
||||
1. Edit the translation.
|
||||
|
||||
### Translation process example for French
|
||||
|
||||
To demonstrate the process, review the `messages.fr.xlf` file in the [Example Angular Internationalization application][GuideI18nExample]. The [Example Angular Internationalization application][GuideI18nExample] includes a French translation for you to edit without a special XLIFF editor or knowledge of French.
|
||||
To demonstrate the process, review the `messages.fr.xlf` file in the [Example Angular Internationalization application][GuideI18nExample]. The [Example Angular Internationalization application][GuideI18nExample] includes a French translation for you to edit without a special XLIFF editor or knowledge of French.
|
||||
|
||||
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.
|
||||
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 (<trans-unit>)" 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.
|
||||
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 (<trans-unit>, 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.
|
||||
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.
|
||||
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 (<trans-unit>)" 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.
|
||||
|
|
@ -163,9 +163,9 @@ HELPFUL: For language plural rules, see [CLDR plural rules][GithubUnicodeOrgCldr
|
|||
|
||||
To translate a `plural`, translate the ICU format match values.
|
||||
|
||||
* `just now`
|
||||
* `one minute ago`
|
||||
* `<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago`
|
||||
- `just now`
|
||||
- `one minute ago`
|
||||
- `<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago`
|
||||
|
||||
The following example displays the way to translate.
|
||||
|
||||
|
|
@ -222,32 +222,22 @@ The following example displays both translation units after translating.
|
|||
<docs-pill href="guide/i18n/merge" title="Merge translations into the app"/>
|
||||
</docs-pill-row>
|
||||
|
||||
[CliMain]: cli "CLI Overview and Command Reference | Angular"
|
||||
[CliExtractI18n]: cli/extract-i18n "ng extract-i18n | CLI | Angular"
|
||||
|
||||
[GuideI18nCommonPrepare]: guide/i18n/prepare "Prepare component for translation | Angular"
|
||||
[GuideI18nCommonPrepareAddHelpfulDescriptionsAndMeanings]: guide/i18n/prepare#add-helpful-descriptions-and-meanings "Add helpful descriptions and meanings - Prepare component for translation | Angular"
|
||||
|
||||
[GuideI18nCommonTranslationFilesCreateATranslationFileForEachLanguage]: guide/i18n/translation-files#create-a-translation-file-for-each-language "Create a translation file for each language - Work with translation files | Angular"
|
||||
[GuideI18nCommonTranslationFilesExtractTheSourceLanguageFile]: guide/i18n/translation-files#extract-the-source-language-file "Extract the source language file - Work with translation files | Angular"
|
||||
[GuideI18nCommonTranslationFilesTranslateAlternateExpressions]: guide/i18n/translation-files#translate-alternate-expressions "Translate alternate expressions - Work with translation files | Angular"
|
||||
[GuideI18nCommonTranslationFilesTranslateEachTranslationFile]: guide/i18n/translation-files#translate-each-translation-file "Translate each translation file - Work with translation files | Angular"
|
||||
[GuideI18nCommonTranslationFilesTranslateNestedExpressions]: guide/i18n/translation-files#translate-nested-expressions "Translate nested expressions - Work with translation files | Angular"
|
||||
[GuideI18nCommonTranslationFilesTranslatePlurals]: guide/i18n/translation-files#translate-plurals "Translate plurals - Work with translation files | Angular"
|
||||
|
||||
[GuideI18nExample]: guide/i18n/example "Example Angular Internationalization application | Angular"
|
||||
|
||||
[GuideI18nOptionalManageMarkedText]: guide/i18n/manage-marked-text "Manage marked text with custom IDs | Angular"
|
||||
|
||||
[GithubGoogleAppResourceBundleWikiApplicationresourcebundlespecification]: https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification "ApplicationResourceBundleSpecification | google/app-resource-bundle | GitHub"
|
||||
|
||||
[GithubUnicodeOrgCldrStagingChartsLatestSupplementalLanguagePluralRulesHtml]: https://cldr.unicode.org/index/cldr-spec/plural-rules "Language Plural Rules - CLDR Charts | Unicode | GitHub"
|
||||
|
||||
[JsonMain]: https://www.json.org "Introducing JSON | JSON"
|
||||
|
||||
[OasisOpenDocsXliffXliffCoreXliffCoreHtml]: https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html "XLIFF Version 1.2 Specification | Oasis Open Docs"
|
||||
[OasisOpenDocsXliffXliffCoreV20Cos01XliffCoreV20Cose01Html]: http://docs.oasis-open.org/xliff/xliff-core/v2.0/cos01/xliff-core-v2.0-cos01.html "XLIFF Version 2.0 | Oasis Open Docs"
|
||||
|
||||
[UnicodeCldrDevelopmentDevelopmentProcessDesignProposalsXmb]: http://cldr.unicode.org/development/development-process/design-proposals/xmb "XMB | CLDR - Unicode Common Locale Data Repository | Unicode"
|
||||
|
||||
[WikipediaWikiXliff]: https://en.wikipedia.org/wiki/XLIFF "XLIFF | Wikipedia"
|
||||
[CliMain]: cli 'CLI Overview and Command Reference | Angular'
|
||||
[CliExtractI18n]: cli/extract-i18n 'ng extract-i18n | CLI | Angular'
|
||||
[GuideI18nCommonPrepare]: guide/i18n/prepare 'Prepare component for translation | Angular'
|
||||
[GuideI18nCommonPrepareAddHelpfulDescriptionsAndMeanings]: guide/i18n/prepare#add-helpful-descriptions-and-meanings 'Add helpful descriptions and meanings - Prepare component for translation | Angular'
|
||||
[GuideI18nCommonTranslationFilesCreateATranslationFileForEachLanguage]: guide/i18n/translation-files#create-a-translation-file-for-each-language 'Create a translation file for each language - Work with translation files | Angular'
|
||||
[GuideI18nCommonTranslationFilesExtractTheSourceLanguageFile]: guide/i18n/translation-files#extract-the-source-language-file 'Extract the source language file - Work with translation files | Angular'
|
||||
[GuideI18nCommonTranslationFilesTranslateAlternateExpressions]: guide/i18n/translation-files#translate-alternate-expressions 'Translate alternate expressions - Work with translation files | Angular'
|
||||
[GuideI18nCommonTranslationFilesTranslateEachTranslationFile]: guide/i18n/translation-files#translate-each-translation-file 'Translate each translation file - Work with translation files | Angular'
|
||||
[GuideI18nCommonTranslationFilesTranslateNestedExpressions]: guide/i18n/translation-files#translate-nested-expressions 'Translate nested expressions - Work with translation files | Angular'
|
||||
[GuideI18nCommonTranslationFilesTranslatePlurals]: guide/i18n/translation-files#translate-plurals 'Translate plurals - Work with translation files | Angular'
|
||||
[GuideI18nExample]: guide/i18n/example 'Example Angular Internationalization application | Angular'
|
||||
[GuideI18nOptionalManageMarkedText]: guide/i18n/manage-marked-text 'Manage marked text with custom IDs | Angular'
|
||||
[GithubGoogleAppResourceBundleWikiApplicationresourcebundlespecification]: https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification 'ApplicationResourceBundleSpecification | google/app-resource-bundle | GitHub'
|
||||
[GithubUnicodeOrgCldrStagingChartsLatestSupplementalLanguagePluralRulesHtml]: https://cldr.unicode.org/index/cldr-spec/plural-rules 'Language Plural Rules - CLDR Charts | Unicode | GitHub'
|
||||
[JsonMain]: https://www.json.org 'Introducing JSON | JSON'
|
||||
[OasisOpenDocsXliffXliffCoreXliffCoreHtml]: https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html 'XLIFF Version 1.2 Specification | Oasis Open Docs'
|
||||
[OasisOpenDocsXliffXliffCoreV20Cos01XliffCoreV20Cose01Html]: http://docs.oasis-open.org/xliff/xliff-core/v2.0/cos01/xliff-core-v2.0-cos01.html 'XLIFF Version 2.0 | Oasis Open Docs'
|
||||
[UnicodeCldrDevelopmentDevelopmentProcessDesignProposalsXmb]: http://cldr.unicode.org/development/development-process/design-proposals/xmb 'XMB | CLDR - Unicode Common Locale Data Repository | Unicode'
|
||||
[WikipediaWikiXliff]: https://en.wikipedia.org/wiki/XLIFF 'XLIFF | Wikipedia'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
IMPORTANT: The Angular team recommends using [standalone components](guide/components/anatomy-of-components#-imports-in-the-component-decorator) instead of `NgModule` for all new code. Use this guide to understand existing code built with `@NgModule`.
|
||||
|
||||
An NgModule is a class marked by the `@NgModule` decorator. This decorator accepts *metadata* that tells Angular how to compile component templates and configure dependency injection.
|
||||
An NgModule is a class marked by the `@NgModule` decorator. This decorator accepts _metadata_ that tells Angular how to compile component templates and configure dependency injection.
|
||||
|
||||
```typescript
|
||||
import {NgModule} from '@angular/core';
|
||||
|
|
@ -14,8 +14,9 @@ export class CustomMenuModule { }
|
|||
```
|
||||
|
||||
An NgModule has two main responsibilities:
|
||||
* Declaring components, directives, and pipes that belong to the NgModule
|
||||
* Add providers to the injector for components, directives, and pipes that import the NgModule
|
||||
|
||||
- Declaring components, directives, and pipes that belong to the NgModule
|
||||
- Add providers to the injector for components, directives, and pipes that import the NgModule
|
||||
|
||||
## Declarations
|
||||
|
||||
|
|
@ -80,27 +81,27 @@ The `imports` array accepts other NgModules, as well as standalone components, d
|
|||
|
||||
An NgModule can _export_ its declared components, directives, and pipes such that they're available to other components and NgModules.
|
||||
|
||||
```typescript
|
||||
```typescript
|
||||
@NgModule({
|
||||
imports: [PopupTrigger, SelectionIndicator],
|
||||
declarations: [CustomMenu, CustomMenuItem],
|
||||
imports: [PopupTrigger, SelectionIndicator],
|
||||
declarations: [CustomMenu, CustomMenuItem],
|
||||
|
||||
// Make CustomMenu and CustomMenuItem available to
|
||||
// components and NgModules that import CustomMenuModule.
|
||||
exports: [CustomMenu, CustomMenuItem],
|
||||
// Make CustomMenu and CustomMenuItem available to
|
||||
// components and NgModules that import CustomMenuModule.
|
||||
exports: [CustomMenu, CustomMenuItem],
|
||||
})
|
||||
export class CustomMenuModule { }
|
||||
```
|
||||
|
||||
The `exports` property is not limited to declarations, however. An NgModule can also export any other components, directives, pipes, and NgModules that it imports.
|
||||
|
||||
```typescript
|
||||
```typescript
|
||||
@NgModule({
|
||||
imports: [PopupTrigger, SelectionIndicator],
|
||||
declarations: [CustomMenu, CustomMenuItem],
|
||||
imports: [PopupTrigger, SelectionIndicator],
|
||||
declarations: [CustomMenu, CustomMenuItem],
|
||||
|
||||
// Also make PopupTrigger available to any component or NgModule that imports CustomMenuModule.
|
||||
exports: [CustomMenu, CustomMenuItem, PopupTrigger],
|
||||
// Also make PopupTrigger available to any component or NgModule that imports CustomMenuModule.
|
||||
exports: [CustomMenu, CustomMenuItem, PopupTrigger],
|
||||
})
|
||||
export class CustomMenuModule { }
|
||||
```
|
||||
|
|
@ -110,8 +111,9 @@ export class CustomMenuModule { }
|
|||
TIP: See the [Dependency Injection guide](guide/di) for information on dependency injection and providers.
|
||||
|
||||
An `NgModule` can specify `providers` for injected dependencies. These providers are available to:
|
||||
* Any standalone component, directive, or pipe that imports the NgModule, and
|
||||
* The `declarations` and `providers` of any _other_ NgModule that imports the NgModule.
|
||||
|
||||
- Any standalone component, directive, or pipe that imports the NgModule, and
|
||||
- The `declarations` and `providers` of any _other_ NgModule that imports the NgModule.
|
||||
|
||||
```typescript
|
||||
@NgModule({
|
||||
|
|
@ -133,10 +135,11 @@ export class UserProfileModule { }
|
|||
```
|
||||
|
||||
In the example above:
|
||||
* The `CustomMenuModule` provides `OverlayManager`.
|
||||
* The `CustomMenu` and `CustomMenuItem` components can inject `OverlayManager` because they're declared in `CustomMenuModule`.
|
||||
* `UserProfile` can inject `OverlayManager` because its NgModule imports `CustomMenuModule`.
|
||||
* `UserDataClient` can inject `OverlayManager` because its NgModule imports `CustomMenuModule`.
|
||||
|
||||
- The `CustomMenuModule` provides `OverlayManager`.
|
||||
- The `CustomMenu` and `CustomMenuItem` components can inject `OverlayManager` because they're declared in `CustomMenuModule`.
|
||||
- `UserProfile` can inject `OverlayManager` because its NgModule imports `CustomMenuModule`.
|
||||
- `UserDataClient` can inject `OverlayManager` because its NgModule imports `CustomMenuModule`.
|
||||
|
||||
### The `forRoot` and `forChild` pattern
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ Learn about different ways you can optimize the performance of your application
|
|||
|
||||
One of the top priorities of any developer is ensuring that their application is as performant as possible. These guides are here to help you follow best practices for building performant applications by taking advantage of different rendering strategies.
|
||||
|
||||
| Guides Types | Description |
|
||||
| :---------------------------------------- | :--------------------------------------------------------------------------------------------------------- |
|
||||
| [Server and hybrid rendering](/guide/ssr) | Learn how to leverage rendering pages on the server to improve load times. |
|
||||
| [Hydration](/guide/hydration) | A process to improve application performance by restoring its state after server-side rendering and reusing existing DOM structure as much as possible. |
|
||||
| Guides Types | Description |
|
||||
| :---------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Server and hybrid rendering](/guide/ssr) | Learn how to leverage rendering pages on the server to improve load times. |
|
||||
| [Hydration](/guide/hydration) | A process to improve application performance by restoring its state after server-side rendering and reusing existing DOM structure as much as possible. |
|
||||
| [Incremental Hydration](/guide/incremental-hydration) | Incremental hydration is an advanced type of hydration that can leave sections of your application dehydrated and incrementally trigger hydration of those sections as they are needed. |
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import { Event, Router, NavigationStart, NavigationEnd } from '@angular/router';
|
|||
@Component({ ... })
|
||||
export class RouterEventsComponent {
|
||||
private readonly router = inject(Router);
|
||||
|
||||
|
||||
constructor() {
|
||||
// Subscribe to router events and react to events
|
||||
this.router.events.pipe(takeUntilDestroyed()).subscribe((event: Event) => {
|
||||
|
|
@ -102,7 +102,7 @@ import { map } from 'rxjs/operators';
|
|||
})
|
||||
export class AppComponent {
|
||||
private router = inject(Router);
|
||||
|
||||
|
||||
readonly loading = toSignal(
|
||||
this.router.events.pipe(
|
||||
map(() => !!this.router.getCurrentNavigation())
|
||||
|
|
|
|||
|
|
@ -12,73 +12,73 @@ You can also configure routes to guard against unexpected or unauthorized behavi
|
|||
|
||||
## Objectives
|
||||
|
||||
* Organize a sample application's features into modules.
|
||||
* Define how to navigate to a component.
|
||||
* Pass information to a component using a parameter.
|
||||
* Structure routes by nesting several routes.
|
||||
* Check whether users can access a route.
|
||||
* Control whether the application can discard unsaved changes.
|
||||
* Improve performance by pre-fetching route data and lazy loading feature modules.
|
||||
* Require specific criteria to load components.
|
||||
- Organize a sample application's features into modules.
|
||||
- Define how to navigate to a component.
|
||||
- Pass information to a component using a parameter.
|
||||
- Structure routes by nesting several routes.
|
||||
- Check whether users can access a route.
|
||||
- Control whether the application can discard unsaved changes.
|
||||
- Improve performance by pre-fetching route data and lazy loading feature modules.
|
||||
- Require specific criteria to load components.
|
||||
|
||||
## Create a sample application
|
||||
|
||||
Using the Angular CLI, create a new application, *angular-router-sample*.
|
||||
This application will have two components: *crisis-list* and *heroes-list*.
|
||||
Using the Angular CLI, create a new application, _angular-router-sample_.
|
||||
This application will have two components: _crisis-list_ and _heroes-list_.
|
||||
|
||||
1. Create a new Angular project, *angular-router-sample*.
|
||||
1. Create a new Angular project, _angular-router-sample_.
|
||||
|
||||
<docs-code language="shell">
|
||||
ng new angular-router-sample
|
||||
</docs-code>
|
||||
<docs-code language="shell">
|
||||
ng new angular-router-sample
|
||||
</docs-code>
|
||||
|
||||
When prompted with `Would you like to add Angular routing?`, select `N`.
|
||||
When prompted with `Would you like to add Angular routing?`, select `N`.
|
||||
|
||||
When prompted with `Which stylesheet format would you like to use?`, select `CSS`.
|
||||
When prompted with `Which stylesheet format would you like to use?`, select `CSS`.
|
||||
|
||||
After a few moments, a new project, `angular-router-sample`, is ready.
|
||||
After a few moments, a new project, `angular-router-sample`, is ready.
|
||||
|
||||
1. From your terminal, navigate to the `angular-router-sample` directory.
|
||||
1. Create a component, *crisis-list*.
|
||||
1. Create a component, _crisis-list_.
|
||||
|
||||
<docs-code language="shell">
|
||||
ng generate component crisis-list
|
||||
</docs-code>
|
||||
<docs-code language="shell">
|
||||
ng generate component crisis-list
|
||||
</docs-code>
|
||||
|
||||
1. In your code editor, locate the file, `crisis-list.component.html` and replace the placeholder content with the following HTML.
|
||||
|
||||
<docs-code header="src/app/crisis-list/crisis-list.component.html" path="adev/src/content/examples/router-tutorial/src/app/crisis-list/crisis-list.component.html"/>
|
||||
<docs-code header="src/app/crisis-list/crisis-list.component.html" path="adev/src/content/examples/router-tutorial/src/app/crisis-list/crisis-list.component.html"/>
|
||||
|
||||
1. Create a second component, *heroes-list*.
|
||||
1. Create a second component, _heroes-list_.
|
||||
|
||||
<docs-code language="shell">
|
||||
ng generate component heroes-list
|
||||
</docs-code>
|
||||
<docs-code language="shell">
|
||||
ng generate component heroes-list
|
||||
</docs-code>
|
||||
|
||||
1. In your code editor, locate the file, `heroes-list.component.html` and replace the placeholder content with the following HTML.
|
||||
|
||||
<docs-code header="src/app/heroes-list/heroes-list.component.html" path="adev/src/content/examples/router-tutorial/src/app/heroes-list/heroes-list.component.html"/>
|
||||
<docs-code header="src/app/heroes-list/heroes-list.component.html" path="adev/src/content/examples/router-tutorial/src/app/heroes-list/heroes-list.component.html"/>
|
||||
|
||||
1. In your code editor, open the file, `app.component.html` and replace its contents with the following HTML.
|
||||
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="setup"/>
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="setup"/>
|
||||
|
||||
1. Verify that your new application runs as expected by running the `ng serve` command.
|
||||
|
||||
<docs-code language="shell">
|
||||
ng serve
|
||||
</docs-code>
|
||||
<docs-code language="shell">
|
||||
ng serve
|
||||
</docs-code>
|
||||
|
||||
1. Open a browser to `http://localhost:4200`.
|
||||
|
||||
You should see a single web page, consisting of a title and the HTML of your two components.
|
||||
You should see a single web page, consisting of a title and the HTML of your two components.
|
||||
|
||||
## Define your routes
|
||||
|
||||
In this section, you'll define two routes:
|
||||
|
||||
* The route `/crisis-center` opens the `crisis-center` component.
|
||||
* The route `/heroes-list` opens the `heroes-list` component.
|
||||
- The route `/crisis-center` opens the `crisis-center` component.
|
||||
- The route `/heroes-list` opens the `heroes-list` component.
|
||||
|
||||
A route definition is a JavaScript object.
|
||||
Each route typically has two properties.
|
||||
|
|
@ -88,18 +88,18 @@ The second property, `component`, is a string that specifies what component your
|
|||
1. From your code editor, create and open the `app.routes.ts` file.
|
||||
1. Create and export a routes list for your application:
|
||||
|
||||
```ts
|
||||
import {Routes} from '@angular/router';
|
||||
```ts
|
||||
import {Routes} from '@angular/router';
|
||||
|
||||
export const routes = [];
|
||||
```
|
||||
export const routes = [];
|
||||
```
|
||||
|
||||
1. Add two routes for your first two components:
|
||||
|
||||
```ts
|
||||
{path: 'crisis-list', component: CrisisListComponent},
|
||||
{path: 'heroes-list', component: HeroesListComponent},
|
||||
```
|
||||
```ts
|
||||
{path: 'crisis-list', component: CrisisListComponent},
|
||||
{path: 'heroes-list', component: HeroesListComponent},
|
||||
```
|
||||
|
||||
This routes list is an array of JavaScript objects, with each object defining the properties of a route.
|
||||
|
||||
|
|
@ -112,16 +112,16 @@ You import this provider function from `@angular/router`.
|
|||
1. From your code editor, open the `app.config.ts` file.
|
||||
1. Add the following import statements:
|
||||
|
||||
```ts
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
```
|
||||
```ts
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
```
|
||||
|
||||
1. Update the providers in the `appConfig`:
|
||||
|
||||
```ts
|
||||
providers: [provideRouter(routes)]
|
||||
```
|
||||
```ts
|
||||
providers: [provideRouter(routes)]
|
||||
```
|
||||
|
||||
For `NgModule` based applications, put the `provideRouter` in the `providers` list of the `AppModule`, or whichever module is passed to `bootstrapModule` in the application.
|
||||
|
||||
|
|
@ -136,17 +136,17 @@ To implement this functionality, you add the `router-outlet` directive to your t
|
|||
1. From your code editor, open the `app.component.html` file.
|
||||
1. Delete the following lines.
|
||||
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="components"/>
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="components"/>
|
||||
|
||||
1. Add the `router-outlet` directive.
|
||||
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="router-outlet"/>
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="router-outlet"/>
|
||||
|
||||
1. Add `RouterOutlet` to the imports of the `AppComponent` in `app.component.ts`
|
||||
|
||||
```ts
|
||||
imports: [RouterOutlet],
|
||||
```
|
||||
```ts
|
||||
imports: [RouterOutlet],
|
||||
```
|
||||
|
||||
View your updated application in your browser.
|
||||
You should see only the application title.
|
||||
|
|
@ -176,16 +176,16 @@ You'll add that functionality in the next section.
|
|||
|
||||
1. Open the `app.component.html` file and add the following HTML below the title.
|
||||
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="nav"/>
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="nav"/>
|
||||
|
||||
This HTML uses an Angular directive, `routerLink`.
|
||||
This directive connects the routes you defined to your template files.
|
||||
This HTML uses an Angular directive, `routerLink`.
|
||||
This directive connects the routes you defined to your template files.
|
||||
|
||||
1. Add the `RouterLink` directive to the imports list of `AppComponent` in `app.component.ts`.
|
||||
|
||||
1. Open the `app.component.css` file and add the following styles.
|
||||
|
||||
<docs-code header="src/app/app.component.css" path="adev/src/content/examples/router-tutorial/src/app/app.component.css"/>
|
||||
<docs-code header="src/app/app.component.css" path="adev/src/content/examples/router-tutorial/src/app/app.component.css"/>
|
||||
|
||||
If you view your application in the browser, you should see these two links.
|
||||
When you click on a link, the corresponding component appears.
|
||||
|
|
@ -198,7 +198,8 @@ Add this functionality using Angular's `routerLinkActive` directive.
|
|||
1. From your code editor, open the `app.component.html` file.
|
||||
1. Update the anchor tags to include the `routerLinkActive` directive.
|
||||
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="routeractivelink"/>
|
||||
<docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="routeractivelink"/>
|
||||
|
||||
1. Add the `RouterLinkActive` directive to the `imports` list of `AppComponent` in `app.component.ts`.
|
||||
|
||||
View your application again.
|
||||
|
|
@ -215,17 +216,17 @@ In this step of the tutorial, you add a route that redirects the user to display
|
|||
1. From your code editor, open the `app.routes.ts` file.
|
||||
1. Update the `routes` section as follows.
|
||||
|
||||
```ts
|
||||
{path: '', redirectTo: '/heroes-list', pathMatch: 'full'},
|
||||
```
|
||||
```ts
|
||||
{path: '', redirectTo: '/heroes-list', pathMatch: 'full'},
|
||||
```
|
||||
|
||||
Notice that this new route uses an empty string as its path.
|
||||
In addition, it replaces the `component` property with two new ones:
|
||||
Notice that this new route uses an empty string as its path.
|
||||
In addition, it replaces the `component` property with two new ones:
|
||||
|
||||
| Properties | Details |
|
||||
|:--- |:--- |
|
||||
| `redirectTo` | This property instructs Angular to redirect from an empty path to the `heroes-list` path. |
|
||||
| `pathMatch` | This property instructs Angular on how much of the URL to match. For this tutorial, you should set this property to `full`. This strategy is recommended when you have an empty string for a path. For more information about this property, see the [Route API documentation](api/router/Route). |
|
||||
| Properties | Details |
|
||||
| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `redirectTo` | This property instructs Angular to redirect from an empty path to the `heroes-list` path. |
|
||||
| `pathMatch` | This property instructs Angular on how much of the URL to match. For this tutorial, you should set this property to `full`. This strategy is recommended when you have an empty string for a path. For more information about this property, see the [Route API documentation](api/router/Route). |
|
||||
|
||||
Now when you open your application, it displays the `heroes-list` component by default.
|
||||
|
||||
|
|
@ -237,23 +238,23 @@ In this section, you'll create a 404 page and update your route configuration to
|
|||
|
||||
1. From the terminal, create a new component, `PageNotFound`.
|
||||
|
||||
<docs-code language="shell">
|
||||
ng generate component page-not-found
|
||||
</docs-code>
|
||||
<docs-code language="shell">
|
||||
ng generate component page-not-found
|
||||
</docs-code>
|
||||
|
||||
1. From your code editor, open the `page-not-found.component.html` file and replace its contents with the following HTML.
|
||||
|
||||
<docs-code header="src/app/page-not-found/page-not-found.component.html" path="adev/src/content/examples/router-tutorial/src/app/page-not-found/page-not-found.component.html"/>
|
||||
<docs-code header="src/app/page-not-found/page-not-found.component.html" path="adev/src/content/examples/router-tutorial/src/app/page-not-found/page-not-found.component.html"/>
|
||||
|
||||
1. Open the `app.routes.ts` file and add the following route to the routes list:
|
||||
|
||||
```ts
|
||||
{path: '**', component: PageNotFoundComponent}
|
||||
```
|
||||
```ts
|
||||
{path: '**', component: PageNotFoundComponent}
|
||||
```
|
||||
|
||||
The new route uses a path, `**`.
|
||||
This path is how Angular identifies a wildcard route.
|
||||
Any route that does not match an existing route in your configuration will use this route.
|
||||
The new route uses a path, `**`.
|
||||
This path is how Angular identifies a wildcard route.
|
||||
Any route that does not match an existing route in your configuration will use this route.
|
||||
|
||||
IMPORTANT: Notice that the wildcard route is placed at the end of the array.
|
||||
The order of your routes is important, as Angular applies routes in order and uses the first match it finds.
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ The `source` can be any signal, such as a `computed` or component `input`. When
|
|||
|
||||
The `computation` is a function that receives the new value of `source` and a `previous` object. The `previous` object has two properties— `previous.source` is the previous value of `source`, and `previous.value` is the previous value of the `linkedSignal`. You can use these previous values to decide the new result of the computation.
|
||||
|
||||
HELPFUL: When using the `previous` parameter, it is necessary to provide the generic type arguments of `linkedSignal` explicitly. The first generic type corresponds with the type of `source` and the second generic type determines the output type of `computation`.
|
||||
HELPFUL: When using the `previous` parameter, it is necessary to provide the generic type arguments of `linkedSignal` explicitly. The first generic type corresponds with the type of `source` and the second generic type determines the output type of `computation`.
|
||||
|
||||
## Custom equality comparison
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ The `status` signal provides a specific `ResourceStatus` that describes the stat
|
|||
| ------------- | :---------------- | ---------------------------------------------------------------------------- |
|
||||
| `'idle'` | `undefined` | The resource has no valid request and the loader has not run. |
|
||||
| `'error'` | `undefined` | The loader has encountered an error. |
|
||||
| `'loading'` | `undefined` | The loader is running as a result of the `params` value changing. |
|
||||
| `'loading'` | `undefined` | The loader is running as a result of the `params` value changing. |
|
||||
| `'reloading'` | Previous value | The loader is running as a result calling of the resource's `reload` method. |
|
||||
| `'resolved'` | Resolved value | The loader has completed. |
|
||||
| `'local'` | Locally set value | The resource's value has been set locally via `.set()` or `.update()` |
|
||||
|
|
|
|||
|
|
@ -66,4 +66,4 @@ Run your build process with `ng serve` and you should see the styled heading.
|
|||
|
||||
## Additional Resources
|
||||
|
||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
||||
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
||||
|
|
|
|||
|
|
@ -30,18 +30,19 @@ Bindings that change over time should read values from [signals](/guide/signals)
|
|||
```angular-ts
|
||||
@Component({
|
||||
template: `
|
||||
<!-- Does not necessarily update when `welcomeMessage` changes. -->
|
||||
<p>{{ welcomeMessage }}</p>
|
||||
<!-- Does not necessarily update when `welcomeMessage` changes. -->
|
||||
<p>{{ welcomeMessage }}</p>
|
||||
|
||||
<p>Your color preference is {{ theme() }}.</p> <!-- Always updates when the value of the `name` signal changes. -->
|
||||
`
|
||||
...
|
||||
})
|
||||
export class AppComponent {
|
||||
welcomeMessage = "Welcome, enjoy this app that we built for you";
|
||||
welcomeMessage = "Welcome, enjoy this app that we built for you";
|
||||
theme = signal('dark');
|
||||
}
|
||||
```
|
||||
|
||||
For more details, see the [Signals guide](/guide/signals).
|
||||
|
||||
Continuing the theme example, if a user clicks on a button that updates the `theme` signal to `'light'` after the page loads, the page updates accordingly to:
|
||||
|
|
@ -193,7 +194,7 @@ When binding `class` to an array or an object, Angular compares the previous val
|
|||
|
||||
If an element has multiple bindings for the same CSS class, Angular resolves collisions by following its style precedence order.
|
||||
|
||||
NOTE: Class bindings do not support space-separated class names in a single key. They also don't support mutations on objects as the reference of the binding remains the same. If you need one or the other, use the [ngClass](/api/common/NgClass) directive.
|
||||
NOTE: Class bindings do not support space-separated class names in a single key. They also don't support mutations on objects as the reference of the binding remains the same. If you need one or the other, use the [ngClass](/api/common/NgClass) directive.
|
||||
|
||||
### CSS style properties
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Angular supports a subset of [literal values](https://developer.mozilla.org/en-U
|
|||
### Unsupported value literals
|
||||
|
||||
| Literal type | Example values |
|
||||
|--------------|----------------|
|
||||
| ------------ | -------------- |
|
||||
| BigInt | `1n` |
|
||||
|
||||
## Globals
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
# Testing Attribute Directives
|
||||
|
||||
An *attribute directive* modifies the behavior of an element, component or another directive.
|
||||
An _attribute directive_ modifies the behavior of an element, component or another directive.
|
||||
Its name reflects the way the directive is applied: as an attribute on a host element.
|
||||
|
||||
## Testing the `HighlightDirective`
|
||||
|
|
@ -22,7 +21,7 @@ Testing the specific use of the `HighlightDirective` within the `AboutComponent`
|
|||
However, testing a single use case is unlikely to explore the full range of a directive's capabilities.
|
||||
Finding and testing all components that use the directive is tedious, brittle, and almost as unlikely to afford full coverage.
|
||||
|
||||
*Class-only tests* might be helpful, but attribute directives like this one tend to manipulate the DOM.
|
||||
_Class-only tests_ might be helpful, but attribute directives like this one tend to manipulate the DOM.
|
||||
Isolated unit tests don't touch the DOM and, therefore, do not inspire confidence in the directive's efficacy.
|
||||
|
||||
A better solution is to create an artificial test component that demonstrates all ways to apply the directive.
|
||||
|
|
@ -40,14 +39,14 @@ Here are some tests of this component:
|
|||
|
||||
A few techniques are noteworthy:
|
||||
|
||||
* The `By.directive` predicate is a great way to get the elements that have this directive *when their element types are unknown*
|
||||
* The [`:not` pseudo-class](https://developer.mozilla.org/docs/Web/CSS/:not) in `By.css('h2:not([highlight])')` helps find `<h2>` elements that *do not* have the directive.
|
||||
`By.css('*:not([highlight])')` finds *any* element that does not have the directive.
|
||||
- The `By.directive` predicate is a great way to get the elements that have this directive _when their element types are unknown_
|
||||
- The [`:not` pseudo-class](https://developer.mozilla.org/docs/Web/CSS/:not) in `By.css('h2:not([highlight])')` helps find `<h2>` elements that _do not_ have the directive.
|
||||
`By.css('*:not([highlight])')` finds _any_ element that does not have the directive.
|
||||
|
||||
* `DebugElement.styles` affords access to element styles even in the absence of a real browser, thanks to the `DebugElement` abstraction.
|
||||
But feel free to exploit the `nativeElement` when that seems easier or more clear than the abstraction.
|
||||
- `DebugElement.styles` affords access to element styles even in the absence of a real browser, thanks to the `DebugElement` abstraction.
|
||||
But feel free to exploit the `nativeElement` when that seems easier or more clear than the abstraction.
|
||||
|
||||
* Angular adds a directive to the injector of the element to which it is applied.
|
||||
The test for the default color uses the injector of the second `<h2>` to get its `HighlightDirective` instance and its `defaultColor`.
|
||||
- Angular adds a directive to the injector of the element to which it is applied.
|
||||
The test for the default color uses the injector of the second `<h2>` to get its `HighlightDirective` instance and its `defaultColor`.
|
||||
|
||||
* `DebugElement.properties` affords access to the artificial custom property that is set by the directive
|
||||
- `DebugElement.properties` affords access to the artificial custom property that is set by the directive
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue