From 9ec73f3d2368190685cf8532fa48f48b83c48130 Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 22 Feb 2026 22:25:38 +0900 Subject: [PATCH 01/55] chore: update origin to 31d3d56 --- origin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/origin b/origin index 71cde39ff..31d3d5649 160000 --- a/origin +++ b/origin @@ -1 +1 @@ -Subproject commit 71cde39ff04dec1fee2ae92e953b7b145a996736 +Subproject commit 31d3d564961b701bda96d94731fbed72c01975fa From db17b8df7e9f1091af5b6873404a2d96c714887c Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 22 Feb 2026 22:26:36 +0900 Subject: [PATCH 02/55] fix: migrate untranslated files --- .../best-practices/performance/overview.md | 43 + .../profiling-with-chrome-devtools.md | 7 +- .../runtime-performance/slow-computations.md | 2 +- .../ecosystem/rxjs-interop/signals-interop.md | 10 +- adev-ja/src/content/guide/animations/css.md | 56 +- .../src/content/guide/animations/migration.md | 100 +- .../src/content/guide/animations/overview.md | 26 +- .../guide/animations/reusable-animations.md | 4 +- .../animations/transition-and-triggers.md | 42 +- .../guide/di/creating-injectable-service.md | 10 +- .../di/debugging-and-troubleshooting-di.md | 1018 +++++++++++++++++ .../guide/di/defining-dependency-providers.md | 68 +- adev-ja/src/content/guide/drag-drop.md | 172 +-- .../guide/forms/signals/async-operations.md | 552 +++++++++ .../signals/designing-your-form-model.md | 16 +- .../content/guide/forms/signals/form-logic.md | 866 ++++++++++++++ .../content/guide/forms/signals/migration.md | 35 +- .../content/guide/i18n/format-data-locale.md | 2 +- .../src/content/guide/ngmodules/overview.md | 4 +- adev-ja/src/content/guide/signals/effect.md | 25 +- .../testing/zone-js-testing-utilities.md | 14 +- .../configs/angular-compiler-options.md | 4 +- .../reference/configs/workspace-config.md | 22 +- .../src/content/reference/errors/NG0204.md | 59 + .../src/content/reference/errors/NG0205.md | 76 ++ .../src/content/reference/errors/NG0207.md | 75 ++ .../src/content/reference/errors/NG0919.md | 18 +- .../reference/extended-diagnostics/NG8102.md | 20 +- .../reference/extended-diagnostics/NG8105.md | 4 +- .../reference/extended-diagnostics/NG8107.md | 12 +- .../reference/extended-diagnostics/NG8108.md | 4 +- .../reference/extended-diagnostics/NG8109.md | 4 +- .../reference/extended-diagnostics/NG8111.md | 4 +- .../reference/extended-diagnostics/NG8114.md | 16 +- .../reference/extended-diagnostics/NG8115.md | 4 +- .../reference/extended-diagnostics/NG8116.md | 11 +- .../reference/extended-diagnostics/NG8117.md | 4 +- .../migrations/common-to-standalone.md | 20 +- .../reference/migrations/control-flow.md | 8 + .../migrations/route-lazy-loading.md | 10 +- .../reference/migrations/self-closing-tags.md | 4 - .../reference/migrations/signal-inputs.md | 9 +- .../reference/migrations/signal-queries.md | 8 +- .../reference/migrations/standalone.md | 36 +- adev-ja/src/content/tools/cli/aot-compiler.md | 60 +- .../content/tools/cli/aot-metadata-errors.md | 58 +- adev-ja/src/content/tools/cli/environments.md | 2 +- .../content/tools/cli/template-typecheck.md | 16 +- .../src/content/tools/devtools/profiler.md | 2 +- .../tools/libraries/angular-package-format.md | 2 + .../tools/libraries/creating-libraries.md | 4 +- .../steps/06-property-binding/README.md | 4 +- .../first-app/steps/09-services/README.md | 2 +- .../first-app/steps/11-details-page/README.md | 2 +- 54 files changed, 3130 insertions(+), 526 deletions(-) create mode 100644 adev-ja/src/content/best-practices/performance/overview.md create mode 100644 adev-ja/src/content/guide/di/debugging-and-troubleshooting-di.md create mode 100644 adev-ja/src/content/guide/forms/signals/async-operations.md create mode 100644 adev-ja/src/content/guide/forms/signals/form-logic.md create mode 100644 adev-ja/src/content/reference/errors/NG0204.md create mode 100644 adev-ja/src/content/reference/errors/NG0205.md create mode 100644 adev-ja/src/content/reference/errors/NG0207.md diff --git a/adev-ja/src/content/best-practices/performance/overview.md b/adev-ja/src/content/best-practices/performance/overview.md new file mode 100644 index 000000000..3ff3ac804 --- /dev/null +++ b/adev-ja/src/content/best-practices/performance/overview.md @@ -0,0 +1,43 @@ +# Performance + +Angular includes many optimizations out of the box, but as applications grow, you may need to fine-tune both how quickly your app loads and how responsive it feels during use. These guides cover the tools and techniques Angular provides to help you build fast applications. + +## Loading performance + +Loading performance determines how quickly your application becomes visible and interactive. Slow loading directly impacts [Core Web Vitals](https://web.dev/vitals/) like Largest Contentful Paint (LCP) and Time to First Byte (TTFB). + +| Technique | What it does | When to use it | +| :------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------- | +| [Lazy-loaded routes](guide/routing/define-routes#lazily-loaded-components-and-routes) | Defers loading route components until navigation, reducing the initial bundle size | Applications with multiple routes where not all are needed on initial load | +| [Deferred loading with `@defer`](best-practices/performance/defer) | Splits components into separate bundles that load on demand | Components not visible on initial render, heavy third-party libraries, below-the-fold content | +| [Image optimization](best-practices/performance/image-optimization) | Prioritizes LCP images, lazy loads others, generates responsive `srcset` attributes | Any application that displays images | +| [Server-side rendering](best-practices/performance/ssr) | Renders pages on the server for faster first paint and better SEO, with [hydration](guide/hydration) to restore interactivity and [incremental hydration](guide/incremental-hydration) to defer hydrating sections until needed | Content-heavy applications, pages that need search engine indexing | + +## Runtime performance + +Runtime performance determines how responsive your application feels after it loads. Angular's change detection system keeps the DOM in sync with your data, and optimizing how and when it runs is the primary lever for improving runtime performance. + +| Technique | What it does | When to use it | +| :-------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | +| [Zoneless change detection](guide/zoneless) | Removes ZoneJS overhead and triggers change detection only when signals or events indicate a change | New applications (default in Angular v21+), or existing applications ready to migrate | +| [Slow computations](best-practices/slow-computations) | Identifies and optimizes expensive template expressions and lifecycle hooks | Profiling reveals specific components causing slow change detection cycles | +| [Skipping component subtrees](best-practices/skipping-subtrees) | Uses `OnPush` change detection to skip unchanged component trees | Applications that need finer control over change detection | +| [Zone pollution](best-practices/zone-pollution) | Prevents unnecessary change detection caused by third-party libraries or timers | Zone-based applications where profiling reveals excessive change detection cycles | + +## Measuring performance + +Identifying what to optimize is just as important as knowing how to optimize it. Angular integrates with browser developer tools to help you find bottlenecks. + +| Tool | What it does | +| :------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Chrome DevTools profiling](best-practices/profiling-with-chrome-devtools) | Records Angular-specific performance data alongside browser profiling, with color-coded flame charts that show component rendering, change detection cycles, and lifecycle hooks | +| [Angular DevTools](tools/devtools) | A browser extension that provides a component tree inspector and a profiler for visualizing change detection cycles | + +## What to optimize first + +If you are unsure where to start, profile your application first using the [Chrome DevTools Angular track](best-practices/profiling-with-chrome-devtools) to identify specific bottlenecks. + +As a general starting point: + +- **Slow initial load** — Use [`@defer`](best-practices/performance/defer) to split large components out of the main bundle, [`NgOptimizedImage`](best-practices/performance/image-optimization) to prioritize above-the-fold images, and [server-side rendering](best-practices/performance/ssr) to deliver content faster. +- **Slow interactions after load** — Check whether [zoneless change detection](guide/zoneless) is enabled, look for [slow computations](best-practices/slow-computations) in templates or lifecycle hooks, and consider [`OnPush`](best-practices/skipping-subtrees) to reduce unnecessary change detection. diff --git a/adev-ja/src/content/best-practices/runtime-performance/profiling-with-chrome-devtools.md b/adev-ja/src/content/best-practices/runtime-performance/profiling-with-chrome-devtools.md index 67a98b65e..d992e529a 100644 --- a/adev-ja/src/content/best-practices/runtime-performance/profiling-with-chrome-devtools.md +++ b/adev-ja/src/content/best-practices/runtime-performance/profiling-with-chrome-devtools.md @@ -25,11 +25,10 @@ You can use the Angular track to better understand how your code runs in the bro You can enable Angular profiling in one of two ways: -1. Run `ng.enableProfiling()` in Chrome's console panel, or -1. Include a call to `enableProfiling()` in your application startup code (imported from `@angular/core`). +1. Run [`ng.enableProfiling()`](api/core/enableProfiling) in Chrome's console panel, or +1. Include a call to [`enableProfiling()`](api/core/enableProfiling) in your application startup code (imported from `@angular/core`). -NOTE: -Angular profiling works exclusively in development mode. +NOTE: Angular profiling works exclusively in development mode. Here is an example of how you can enable the integration in the application bootstrap to capture all possible events: diff --git a/adev-ja/src/content/best-practices/runtime-performance/slow-computations.md b/adev-ja/src/content/best-practices/runtime-performance/slow-computations.md index 744080ee3..b13506998 100644 --- a/adev-ja/src/content/best-practices/runtime-performance/slow-computations.md +++ b/adev-ja/src/content/best-practices/runtime-performance/slow-computations.md @@ -19,7 +19,7 @@ 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. +- **Caching using pure pipes**. You can move the heavy computation to a pure [pipe](guide/templates/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. diff --git a/adev-ja/src/content/ecosystem/rxjs-interop/signals-interop.md b/adev-ja/src/content/ecosystem/rxjs-interop/signals-interop.md index ee763e37a..9b28a809a 100644 --- a/adev-ja/src/content/ecosystem/rxjs-interop/signals-interop.md +++ b/adev-ja/src/content/ecosystem/rxjs-interop/signals-interop.md @@ -7,10 +7,10 @@ The `@angular/core/rxjs-interop` package offers APIs that help you integrate RxJ Use the `toSignal` function to create a signal which tracks the value of an Observable. It behaves similarly to the `async` pipe in templates, but is more flexible and can be used anywhere in an application. ```angular-ts -import { Component } from '@angular/core'; -import { AsyncPipe } from '@angular/common'; -import { interval } from 'rxjs'; -import { toSignal } from '@angular/core/rxjs-interop'; +import {Component} from '@angular/core'; +import {AsyncPipe} from '@angular/common'; +import {interval} from 'rxjs'; +import {toSignal} from '@angular/core/rxjs-interop'; @Component({ template: `{{ counter() }}`, @@ -47,7 +47,7 @@ If you don't provide an `initialValue`, the resulting signal will return `undefi Some Observables are guaranteed to emit synchronously, such as `BehaviorSubject`. In those cases, you can specify the `requireSync: true` option. -When `requiredSync` is `true`, `toSignal` enforces that the Observable emits synchronously on subscription. This guarantees that the signal always has a value, and no `undefined` type or initial value is required. +When `requireSync` is `true`, `toSignal` enforces that the Observable emits synchronously on subscription. This guarantees that the signal always has a value, and no `undefined` type or initial value is required. ### `manualCleanup` diff --git a/adev-ja/src/content/guide/animations/css.md b/adev-ja/src/content/guide/animations/css.md index 517a78f6f..7f524b3e6 100644 --- a/adev-ja/src/content/guide/animations/css.md +++ b/adev-ja/src/content/guide/animations/css.md @@ -52,10 +52,10 @@ CSSには、アプリケーション内で美しく魅力的なアニメーシ アニメーションは、CSSのスタイルやクラスを切り替えることでトリガーできます。クラスが要素に追加されるとアニメーションが実行され、クラスを削除すると、その要素に定義されているCSSに戻ります。例を示します。 - - - - + + + + ## トランジションとトリガー {#transition-and-triggers} @@ -64,10 +64,10 @@ CSSには、アプリケーション内で美しく魅力的なアニメーシ CSS Gridを使用すると、`height: auto`へのアニメーションを実現できます。 - - - - + + + + すべてのブラウザをサポートする必要がない場合は、`height: auto`をアニメーション化するためのより本質的な解決策である`calc-size()`も確認してください。詳しくは、[MDNのドキュメント](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size)とこの項目に関連する[チュートリアル](https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/)を参照してください。 @@ -76,18 +76,18 @@ CSS Gridを使用すると、`height: auto`へのアニメーションを実現 要素がビューに入るとき、またはビューから出るときのアニメーションを作成できます。まずは、ビューに入るときのアニメーションを見ていきましょう。`animate.enter`を使うと、要素がビューに入るときにアニメーション用のクラスが適用されます。 - - - - + + + + ビューから出るときのアニメーションも、ビューに入るときと同様です。`animate.leave`で、要素がビューから出るときに適用するCSSクラスを指定します。 - - - - + + + + `animate.enter`と`animate.leave`について詳しくは、[EnterとLeaveのアニメーションガイド](guide/animations/enter-and-leave)を参照してください。 @@ -96,10 +96,10 @@ CSS Gridを使用すると、`height: auto`へのアニメーションを実現 インクリメントとデクリメントに合わせてアニメーションするのは、アプリケーションでよくあるパターンです。以下はその例です。 - - - - + + + + ### アニメーション(またはすべてのアニメーション)を無効にする {#disabling-an-animation-or-all-animations} @@ -147,10 +147,10 @@ NOTE: これらのコールバックはバブリング(イベント伝播) よくある効果の1つに、リスト内の各要素のアニメーションを段階的に遅らせてカスケード効果を作るものがあります。これは`animation-delay`または`transition-delay`を利用して実現できます。次はそのCSSの例です。 - - - - + + + + ### 並行アニメーション {#parallel-animations} @@ -171,10 +171,10 @@ NOTE: これらのコールバックはバブリング(イベント伝播) `@for`ループ内の要素は削除されて再追加されるため、enterアニメーションとして`@starting-styles`を使用したアニメーションが実行されます。同じ挙動は`animate.enter`でも実現できます。要素が削除されるときにアニメーションするには、以下の例のように`animate.leave`を使用します。 - - - - + + + + ## アニメーションをプログラムで制御する {#programmatic-control-of-animations} diff --git a/adev-ja/src/content/guide/animations/migration.md b/adev-ja/src/content/guide/animations/migration.md index 7872a9b50..e3a3e8dff 100644 --- a/adev-ja/src/content/guide/animations/migration.md +++ b/adev-ja/src/content/guide/animations/migration.md @@ -38,7 +38,7 @@ The animations package allowed you to define various states using the [`state()` #### With Animations Package - + This same behavior can be accomplished natively by using CSS classes either using a keyframe animation or transition styling. @@ -69,17 +69,17 @@ The animations package required specifying triggers using the `trigger()` functi #### With Animations Package - - - + + + #### With Native CSS - - - - + + + + ## Transition and Triggers @@ -97,19 +97,19 @@ The animations package offers the ability to animate things that have been histo #### With Animations Package - - - + + + You can use css-grid to animate to auto height. #### With Native CSS - - - - + + + + If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and (this tutorial)[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/] for more information. @@ -121,29 +121,25 @@ The animations package offered the previously mentioned pattern matching for ent #### With Animations Package - - - + + + -Here's how the same thing can be accomplished without the animations package using `animate.enter`. - #### With Native CSS - - - - + + + + -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 - - - - + + + + For more information on `animate.enter` and `animate.leave`, see the [Enter and Leave animations guide](guide/animations). @@ -155,17 +151,17 @@ Along with the aforementioned `:enter` and `:leave`, there's also `:increment` a #### With Animations Package - - - + + + #### With Native CSS - - - - + + + + ### Parent / Child Animations @@ -226,17 +222,17 @@ The `stagger()` function allowed you to delay the animation of each item in a li #### With Animations Package - - - + + + #### With Native CSS - - - - + + + + ### Parallel Animations @@ -257,20 +253,20 @@ 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< +#### With Animations Package - - - + + + #### With Native CSS - - - - + + + + ## Migrating usages of AnimationPlayer diff --git a/adev-ja/src/content/guide/animations/overview.md b/adev-ja/src/content/guide/animations/overview.md index 04981eaa6..ec2ee0d7c 100644 --- a/adev-ja/src/content/guide/animations/overview.md +++ b/adev-ja/src/content/guide/animations/overview.md @@ -1,6 +1,6 @@ # Introduction to Angular animations -IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations/enter-and-leave). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. +IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. Animation provides the illusion of motion: HTML elements change styling over time. Well-designed animations can make your application more fun and straightforward to use, but they aren't just cosmetic. @@ -52,7 +52,7 @@ For `NgModule` based applications import `BrowserAnimationsModule`, which introd If you plan to use specific animation functions in component files, import those functions from `@angular/animations`. - + See all [available animation functions](guide/legacy-animations#animations-api-summary) at the end of this guide. @@ -61,7 +61,7 @@ See all [available animation functions](guide/legacy-animations#animations-api-s In the component file, add a metadata property called `animations:` within the `@Component()` decorator. You put the trigger that defines an animation within the `animations` metadata property. - + @@ -84,7 +84,7 @@ Run the following command in terminal to generate the component: ng g component open-close ``` -This will create the component at `src/app/open-close.component.ts`. +This will create the component at `src/app/open-close.ts`. ### Animation state and styles @@ -99,11 +99,11 @@ Let's see how Angular's [`state()`](api/animations/state) function works with th In this code snippet, multiple style attributes are set at the same time for the state. In the `open` state, the button has a height of 200 pixels, an opacity of 1, and a yellow background color. - + In the following `closed` state, the button has a height of 100 pixels, an opacity of 0.8, and a background color of blue. - + ### Transitions and timing @@ -171,7 +171,7 @@ HELPFUL: See the Material Design website's topic on [Natural easing curves](http This example provides a state transition from `open` to `closed` with a 1-second transition between states. - + In the preceding code snippet, the `=>` operator indicates unidirectional transitions, and `<=>` is bidirectional. Within the transition, `animate()` specifies how long the transition takes. @@ -179,7 +179,7 @@ In this case, the state change from `open` to `closed` takes 1 second, expressed This example adds a state transition from the `closed` state to the `open` state with a 0.5-second transition animation arc. - + HELPFUL: Some additional notes on using styles within [`state`](api/animations/state) and `transition` functions. @@ -212,7 +212,7 @@ However, it's possible for multiple triggers to be active at once. Animations are defined in the metadata of the component that controls the HTML element to be animated. Put the code that defines your animations under the `animations:` property within the `@Component()` decorator. - + When you've defined an animation trigger for a component, attach it to an element in that component's template by wrapping the trigger name in brackets and preceding it with an `@` symbol. Then, you can bind the trigger to a template expression using standard Angular property binding syntax as shown below, where `triggerName` is the name of the trigger, and `expression` evaluates to a defined animation state. @@ -225,7 +225,7 @@ The animation is executed or triggered when the expression value changes to a ne The following code snippet binds the trigger to the value of the `isOpen` property. - + In this example, when the `isOpen` expression evaluates to a defined state of `open` or `closed`, it notifies the trigger `openClose` of a state change. Then it's up to the `openClose` code to handle the state change and kick off a state change animation. @@ -242,9 +242,9 @@ In the HTML template file, use the trigger name to attach the defined animations Here are the code files discussed in the transition example. - - - + + + ### Summary diff --git a/adev-ja/src/content/guide/animations/reusable-animations.md b/adev-ja/src/content/guide/animations/reusable-animations.md index 350aaa4b4..b1519edfe 100644 --- a/adev-ja/src/content/guide/animations/reusable-animations.md +++ b/adev-ja/src/content/guide/animations/reusable-animations.md @@ -1,6 +1,6 @@ # Reusable animations -IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations/enter-and-leave). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. +IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. This topic provides some examples of how to create reusable animations. @@ -23,7 +23,7 @@ For example, the following snippet exports the animation `trigger`. From this point, you can import reusable animation variables in your component class. For example, the following code snippet imports the `transitionAnimation` variable and uses it via the `useAnimation()` function. - + ## More on Angular animations diff --git a/adev-ja/src/content/guide/animations/transition-and-triggers.md b/adev-ja/src/content/guide/animations/transition-and-triggers.md index 1856a2e24..1e03b72ed 100644 --- a/adev-ja/src/content/guide/animations/transition-and-triggers.md +++ b/adev-ja/src/content/guide/animations/transition-and-triggers.md @@ -1,6 +1,6 @@ # Animation transitions and triggers -IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations/enter-and-leave). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. +IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. This guide goes into depth on special transition states such as the `*` wildcard and `void`. It shows how these special states are used for elements entering and leaving a view. This section also explores multiple animation triggers, animation callbacks, and sequence-based animation using keyframes. @@ -23,11 +23,11 @@ Instead of defining each state-to-state transition pair, any transition to `clos This allows the addition of new states without having to include separate transitions for each one. - + Use a double arrow syntax to specify state-to-state transitions in both directions. - + ### Use wildcard state with multiple transition states @@ -37,7 +37,7 @@ If the button can change from `open` to either `closed` or something like `inPro wildcard state with 3 states - + The `* => *` transition applies when any change between two states takes place. @@ -52,7 +52,7 @@ To do this, list the more specific transitions _before_ `* => *`. Use the wildcard `*` with a style to tell the animation to use whatever the current style value is, and animate with that. Wildcard is a fallback value that's used if the state being animated isn't declared within the trigger. - + ### Void state @@ -76,7 +76,7 @@ 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 - + In the preceding code, you applied the `void` state when the HTML element isn't attached to a view. @@ -105,11 +105,11 @@ As a rule of thumb consider that any element being added to the DOM by Angular p This example has a special trigger for the enter and leave animation called `myInsertRemoveTrigger`. The HTML template contains the following code. - + In the component file, the `:enter` transition sets an initial opacity of 0. It then animates it to change that opacity to 1 as the element is inserted into the view. - + Note that this example doesn't need to use [`state()`](api/animations/state). @@ -121,13 +121,13 @@ Use these to kick off a transition when a numeric value has increased or decreas HELPFUL: The following example uses `query()` and `stagger()` methods. For more information on these methods, see the [complex sequences](guide/legacy-animations/complex-sequences) page. - + ## Boolean values in transitions If a trigger contains a Boolean value as a binding value, then this value can be matched using a `transition()` expression that compares `true` and `false`, or `1` and `0`. - + In the code snippet above, the HTML template binds a `
` element to a trigger named `openClose` with a status expression of `isOpen`, and with possible values of `true` and `false`. This pattern is an alternative to the practice of creating two named states like `open` and `close`. @@ -136,7 +136,7 @@ Inside the `@Component` metadata under the `animations:` property, when the stat In this case, the animation uses whatever height the element already had before the animation started. When the element is `closed`, the element gets animated to a height of 0, which makes it invisible. - + ## Multiple animation triggers @@ -156,8 +156,8 @@ When true, the `@.disabled` binding prevents all animations from rendering. The following code sample shows how to use this feature. - - + + When the `@.disabled` binding is true, the `@childAnimation` trigger doesn't kick off. @@ -177,7 +177,7 @@ Those elements can still animate. To turn off all animations for an Angular application, place the `@.disabled` host binding on the topmost Angular component. - + HELPFUL: Disabling animations application-wide is useful during end-to-end \(E2E\) testing. @@ -186,12 +186,12 @@ HELPFUL: Disabling animations application-wide is useful during end-to-end \(E2E The animation `trigger()` function emits _callbacks_ when it starts and when it finishes. The following example features a component that contains an `openClose` trigger. - + In the HTML template, the animation event is passed back via `$event`, as `@triggerName.start` and `@triggerName.done`, where `triggerName` is the name of the trigger being used. In this example, the trigger `openClose` appears as follows. - + A potential use for animation callbacks could be to cover for a slow API call, such as a database lookup. For example, an **InProgress** button can be set up to have its own looping animation while the backend system operation finishes. @@ -204,7 +204,7 @@ An animation can influence an end user to _perceive_ the operation as faster, ev 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`. - + ## Keyframes @@ -217,7 +217,7 @@ For example, the button, instead of fading, could change color several times ove The code for this color change might look like this. - + ### Offset @@ -233,7 +233,7 @@ Specifying an offset of 0.8 for the middle transition in the preceding example m The code with offsets specified would be as follows. - + You can combine keyframes with `duration`, `delay`, and `easing` within a single animation. @@ -250,7 +250,7 @@ Here's an example of using keyframes to create a pulse effect: The code snippet for this animation might look like this. - + ### Animatable properties and units @@ -285,7 +285,7 @@ In these cases, you can use a special wildcard `*` property value under `style() The following example has a trigger called `shrinkOut`, used when an HTML element leaves the page. The animation takes whatever height the element has before it leaves, and animates from that height to zero. - + ### Keyframes summary diff --git a/adev-ja/src/content/guide/di/creating-injectable-service.md b/adev-ja/src/content/guide/di/creating-injectable-service.md index 1dad19288..30123b8a6 100644 --- a/adev-ja/src/content/guide/di/creating-injectable-service.md +++ b/adev-ja/src/content/guide/di/creating-injectable-service.md @@ -108,20 +108,20 @@ For clarity and maintainability, it is recommended that you define components an To inject a service as a dependency into a component, you can declare a class field representing the dependency and use Angular's [`inject`](/api/core/inject) function to initialize it. -The following example specifies the `HeroService` in the `HeroListComponent`. +The following example specifies the `HeroService` in the `HeroList`. The type of `heroService` is `HeroService`. ```ts import {inject} from '@angular/core'; -export class HeroListComponent { +export class HeroList { private heroService = inject(HeroService); } ``` It is also possible to inject a service into a component using the component's constructor: -```ts {header: 'hero-list.component.ts (constructor signature)'} +```ts {header: 'hero-list.ts (constructor signature)'} constructor(private heroService: HeroService) ``` @@ -155,6 +155,6 @@ In this example, the `getHeroes()` method uses the `Logger` service by logging a ## What's next - - + + diff --git a/adev-ja/src/content/guide/di/debugging-and-troubleshooting-di.md b/adev-ja/src/content/guide/di/debugging-and-troubleshooting-di.md new file mode 100644 index 000000000..e5f9ae964 --- /dev/null +++ b/adev-ja/src/content/guide/di/debugging-and-troubleshooting-di.md @@ -0,0 +1,1018 @@ +# Debugging and troubleshooting dependency injection + +Dependency injection (DI) issues typically stem from configuration mistakes, scope problems, or incorrect usage patterns. This guide helps you identify and resolve common DI problems that developers encounter. + +## Common pitfalls and solutions + +### Services not available where expected + +One of the most common DI issues occurs when you try to inject a service but Angular cannot find it in the current injector or any parent injector. This usually happens when the service is provided in the wrong scope or not provided at all. + +#### Provider scope mismatch + +When you provide a service in a component's `providers` array, Angular creates an instance in that component's injector. This instance is only available to that component and its children. Parent components and sibling components cannot access it because they use different injectors. + +```angular-ts {header: 'child-view.ts'} +import {Component} from '@angular/core'; +import {DataStore} from './data-store'; + +@Component({ + selector: 'app-child', + template: '

Child

', + providers: [DataStore], // Only available in this component and its children +}) +export class ChildView {} +``` + +```angular-ts {header: 'parent-view.ts'} +import {Component, inject} from '@angular/core'; +import {DataStore} from './data-store'; + +@Component({ + selector: 'app-parent', + template: '', +}) +export class ParentView { + private dataService = inject(DataStore); // ERROR: Not available to parent +} +``` + +Angular only searches up the hierarchy, never down. Parent components cannot access services provided in child components. + +**Solution:** Provide the service at a higher level (application or parent component). + +```ts {prefer} +import {Injectable} from '@angular/core'; + +@Injectable({providedIn: 'root'}) +export class DataStore { + // Available everywhere +} +``` + +TIP: Use `providedIn: 'root'` by default for services that don't need component-specific state. This makes services available everywhere and enables tree-shaking. + +#### Services and lazy-loaded routes + +When you provide a service in a lazy-loaded route's `providers` array, Angular creates a child injector for that route. This injector and its services only become available after the route loads. Components in the eagerly-loaded parts of your application cannot access these services because they use different injectors that exist before the lazy-loaded injector is created. + +```ts {header: 'feature.routes.ts'} +import {Routes} from '@angular/router'; +import {FeatureClient} from './feature-client'; + +export const featureRoutes: Routes = [ + { + path: 'feature', + providers: [FeatureClient], + loadComponent: () => import('./feature-view'), + }, +]; +``` + +```angular-ts {header: 'eager-view.ts'} +import {Component, inject} from '@angular/core'; +import {FeatureClient} from './feature-client'; + +@Component({ + selector: 'app-eager', + template: '

Eager Component

', +}) +export class EagerView { + private featureService = inject(FeatureClient); // ERROR: Not available yet +} +``` + +Lazy-loaded routes create child injectors that are only available after the route loads. + +NOTE: By default, route injectors and their services persist even after navigating away from the route. They are not destroyed until the application is closed. For automatic cleanup of unused route injectors, see [customizing route behavior](guide/routing/customizing-route-behavior#experimental-automatic-cleanup-of-unused-route-injectors). + +**Solution:** Use `providedIn: 'root'` for services that need to be shared across lazy boundaries. + +```ts {prefer, header: 'Provide at root for shared services'} +import {Injectable} from '@angular/core'; + +@Injectable({providedIn: 'root'}) +export class FeatureClient { + // Available everywhere, including before lazy load +} +``` + +If the service should be lazy-loaded but still available to eager components, inject it only where needed and use optional injection to handle availability. + +### Multiple instances instead of singletons + +You expect one shared instance (singleton) but get separate instances in different components. + +#### Providing in component instead of root + +When you add a service to a component's `providers` array, Angular creates a new instance of that service for each instance of the component. Each component gets its own separate service instance, which means changes in one component don't affect the service instance in other components. This is often unexpected when you want shared state across your application. + +```angular-ts {avoid, header: 'Component-level provider creates multiple instances'} +import {Component, inject} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-profile', + template: '

Profile

', + providers: [UserClient], // Creates new instance per component! +}) +export class UserProfile { + private userService = inject(UserClient); +} + +@Component({ + selector: 'app-settings', + template: '

Settings

', + providers: [UserClient], // Different instance! +}) +export class UserSettings { + private userService = inject(UserClient); +} +``` + +Each component gets its own `UserClient` instance. Changes in one component don't affect the other. + +**Solution:** Use `providedIn: 'root'` for singletons. + +```ts {prefer, header: 'Root-level singleton'} +import {Injectable} from '@angular/core'; + +@Injectable({providedIn: 'root'}) +export class UserClient { + // Single instance shared across all components +} +``` + +#### When multiple instances are intentional + +Sometimes you want separate instances per component for component-specific state. + +```angular-ts {header: 'Intentional: Component-scoped state'} +import {Injectable, signal} from '@angular/core'; + +@Injectable() // No providedIn - must be provided explicitly +export class FormStateStore { + private formData = signal({}); + + setData(data: any) { + this.formData.set(data); + } + + getData() { + return this.formData(); + } +} + +@Component({ + selector: 'app-user-form', + template: '
...
', + providers: [FormStateStore], // Each form gets its own state +}) +export class UserForm { + private formState = inject(FormStateStore); +} +``` + +This pattern is useful for: + +- Form state management (each form has isolated state) +- Component-specific caching +- Temporary data that shouldn't be shared + +### Incorrect inject() usage + +The `inject()` function only works in specific contexts during class construction and factory execution. + +#### Using inject() in lifecycle hooks + +When you call the `inject()` function inside lifecycle hooks like `ngOnInit()`, `ngAfterViewInit()`, or `ngOnDestroy()`, Angular throws an error because these methods run outside the injection context. The injection context is only available during the synchronous execution of class construction, which happens before lifecycle hooks are called. + +```angular-ts {avoid, header: 'inject() in ngOnInit'} +import {Component, inject} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-profile', + template: '

User: {{userName}}

', +}) +export class UserProfile { + userName = ''; + + ngOnInit() { + const userService = inject(UserClient); // ERROR: Not an injection context + this.userName = userService.getUser().name; + } +} +``` + +**Solution:** Capture dependencies and derive values in field initializers. + +```angular-ts {prefer, header: 'Derive values in field initializers'} +import {Component, inject} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-profile', + template: '

User: {{userName}}

', +}) +export class UserProfile { + private userService = inject(UserClient); + userName = this.userService.getUser().name; +} +``` + +#### Using the Injector for deferred injection + +When you need to retrieve services outside an injection context, use the captured `Injector` directly with `injector.get()`: + +```angular-ts +import {Component, inject, Injector} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-profile', + template: '', +}) +export class UserProfile { + private injector = inject(Injector); + + delayedLoad() { + setTimeout(() => { + const userService = this.injector.get(UserClient); + console.log(userService.getUser()); + }, 1000); + } +} +``` + +#### Using runInInjectionContext for callbacks + +Use `runInInjectionContext()` when you need to enable **other code** to call `inject()`. This is useful when accepting callbacks that might use dependency injection: + +```angular-ts +import {Component, inject, Injector, input} from '@angular/core'; + +@Component({ + selector: 'app-data-loader', + template: '', +}) +export class DataLoader { + private injector = inject(Injector); + onLoad = input<() => void>(); + + load() { + const callback = this.onLoad(); + if (callback) { + // Enable the callback to use inject() + this.injector.runInInjectionContext(callback); + } + } +} +``` + +The `runInInjectionContext()` method creates a temporary injection context, allowing code inside the callback to call `inject()`. + +IMPORTANT: Always capture dependencies at the class level when possible. Use `injector.get()` for simple deferred retrieval, and `runInInjectionContext()` only when external code needs to call `inject()`. + +TIP: Use `assertInInjectionContext()` to verify your code is running in a valid injection context. This is useful when creating reusable functions that call `inject()`. See [Asserting the context](guide/di/dependency-injection-context#asserts-the-context) for details. + +### providers vs viewProviders confusion + +The difference between `providers` and `viewProviders` affects content projection scenarios. + +#### Understanding the difference + +**providers:** Available to the component's template AND any content projected into the component (ng-content). + +**viewProviders:** Only available to the component's template, NOT to projected content. + +```angular-ts {header: 'parent-view.ts'} +import {Component, inject} from '@angular/core'; +import {ThemeStore} from './theme-store'; + +@Component({ + selector: 'app-parent', + template: ` +
+

Theme: {{ themeService.theme() }}

+ +
+ `, + providers: [ThemeStore], // Available to content children +}) +export class ParentView { + protected themeService = inject(ThemeStore); +} + +@Component({ + selector: 'app-parent-view', + template: ` +
+

Theme: {{ themeService.theme() }}

+ +
+ `, + viewProviders: [ThemeStore], // NOT available to content children +}) +export class ParentViewOnly { + protected themeService = inject(ThemeStore); +} +``` + +```angular-ts {header: 'child-view.ts'} +import {Component, inject} from '@angular/core'; +import {ThemeStore} from './theme-store'; + +@Component({ + selector: 'app-child', + template: '

Child theme: {{theme()}}

', +}) +export class ChildView { + private themeService = inject(ThemeStore, {optional: true}); + theme = () => this.themeService?.theme() ?? 'none'; +} +``` + +```angular-ts {header: 'app.ts'} +@Component({ + selector: 'app-root', + template: ` + + + + + + + + + + `, +}) +export class App {} +``` + +**When projected into `app-parent`:** The child component can inject `ThemeStore` because `providers` makes it available to projected content. + +**When projected into `app-parent-view`:** The child component cannot inject `ThemeStore` because `viewProviders` restricts it to the parent's template only. + +#### Choosing between providers and viewProviders + +Use `providers` when: + +- The service should be available to projected content +- You want content children to access the service +- You're providing general-purpose services + +Use `viewProviders` when: + +- The service should only be available to your component's template +- You want to hide implementation details from projected content +- You're providing internal services that shouldn't leak out + +**Default recommendation:** Use `providers` unless you have a specific reason to restrict access with `viewProviders`. + +### InjectionToken issues + +When using `InjectionToken` for non-class dependencies, developers often encounter problems related to token identity, type safety, and provider configuration. These issues usually stem from how JavaScript handles object identity and how TypeScript infers types. + +#### Token identity confusion + +When you create a new `InjectionToken` instance, JavaScript creates a unique object in memory. Even if you create another `InjectionToken` with the exact same description string, it's a completely different object. Angular uses the token object's identity (not its description) to match providers with injection points, so tokens with the same description but different object identities cannot access each other's values. + +```ts {header: 'config.token.ts'} +import {InjectionToken} from '@angular/core'; + +export interface AppConfig { + apiUrl: string; +} + +export const APP_CONFIG = new InjectionToken('app config'); +``` + +```ts {header: 'app.config.ts'} +import {APP_CONFIG} from './config.token'; + +export const appConfig: AppConfig = { + apiUrl: 'https://api.example.com', +}; + +bootstrapApplication(App, { + providers: [{provide: APP_CONFIG, useValue: appConfig}], +}); +``` + +```angular-ts {avoid, header: 'feature-view.ts'} +// Creating new token with same description +import {InjectionToken, inject} from '@angular/core'; +import {AppConfig} from './config.token'; + +const APP_CONFIG = new InjectionToken('app config'); + +@Component({ + selector: 'app-feature', + template: '

Feature

', +}) +export class FeatureView { + private config = inject(APP_CONFIG); // ERROR: Different token instance! +} +``` + +Even though both tokens have the description `'app config'`, they are different objects. Angular compares tokens by reference, not by description. + +**Solution:** Import the same token instance. + +```angular-ts {prefer, header: 'feature-view.ts'} +import {inject} from '@angular/core'; +import {APP_CONFIG, AppConfig} from './config.token'; + +@Component({ + selector: 'app-feature', + template: '

API: {{config.apiUrl}}

', +}) +export class FeatureView { + protected config = inject(APP_CONFIG); // Works: Same token instance +} +``` + +TIP: Always export tokens from a shared file and import them everywhere they're needed. Never create multiple `InjectionToken` instances with the same description. + +#### Trying to inject interfaces + +When you define a TypeScript interface, it only exists during compilation for type checking. TypeScript erases all interface definitions when it compiles to JavaScript, so at runtime there's no object for Angular to use as an injection token. If you try to inject an interface type, Angular has nothing to match against the provider configuration. + +```angular-ts {avoid, header: 'Can't inject interface'} +interface UserConfig { + name: string; + email: string; +} + +@Component({ + selector: 'app-profile', + template: '

Profile

', +}) +export class UserProfile { + // ERROR: Interfaces don't exist at runtime + constructor(private config: UserConfig) {} +} +``` + +**Solution:** Use `InjectionToken` for interface types. + +```angular-ts {prefer, header: 'Use InjectionToken for interfaces'} +import {InjectionToken, inject} from '@angular/core'; + +interface UserConfig { + name: string; + email: string; +} + +export const USER_CONFIG = new InjectionToken('user configuration'); + +// Provide the configuration +bootstrapApplication(App, { + providers: [ + { + provide: USER_CONFIG, + useValue: {name: 'Alice', email: 'alice@example.com'}, + }, + ], +}); + +// Inject using the token +@Component({ + selector: 'app-profile', + template: '

User: {{config.name}}

', +}) +export class UserProfile { + protected config = inject(USER_CONFIG); +} +``` + +The `InjectionToken` exists at runtime and can be used for injection, while the `UserConfig` interface provides type safety during development. + +### Circular dependencies + +Circular dependencies occur when services inject each other, creating a cycle that Angular cannot resolve. For detailed explanations and code examples, see [NG0200: Circular dependency](errors/NG0200). + +**Resolution strategies** (in order of preference): + +1. **Restructure** - Extract shared logic to a third service, breaking the cycle +2. **Use events** - Replace direct dependencies with event-based communication (such as `Subject`) +3. **Lazy injection** - Use `Injector.get()` to defer one dependency (last resort) + +NOTE: Do not use `forwardRef()` for service circular dependencies—it only solves circular imports in standalone component configurations. + +## Debugging dependency resolution + +### Understanding the resolution process + +Angular resolves dependencies by walking up the injector hierarchy. When a `NullInjectorError` occurs, understanding this search order helps you identify where to add the missing provider. + +Angular searches in this order: + +1. **Element injector** - The current component or directive +2. **Parent element injectors** - Up the DOM tree through parent components +3. **Environment injector** - The route or application injector +4. **NullInjector** - Throws `NullInjectorError` if not found + +When you see a `NullInjectorError`, the service isn't provided at any level the component can access. Check that: + +- The service has `@Injectable({providedIn: 'root'})`, or +- The service is in a `providers` array the component can reach + +You can modify this search behavior with resolution modifiers like `self`, `skipSelf`, `host`, and `optional`. For complete coverage of resolution rules and modifiers, see the [Hierarchical injectors guide](guide/di/hierarchical-dependency-injection). + +### Using Angular DevTools + +Angular DevTools includes an injector tree inspector that visualizes the entire injector hierarchy and shows which providers are available at each level. For installation and general usage, see the [Angular DevTools injector documentation](tools/devtools/injectors). + +When debugging DI issues, use DevTools to answer these questions: + +- **Is the service provided?** Select the component that fails to inject and check if the service appears in the Injector section. +- **At what level?** Walk up the component tree to find where the service is actually provided (component, route, or application level). +- **Multiple instances?** If a singleton service appears in multiple component injectors, it's likely provided in component `providers` arrays instead of using `providedIn: 'root'`. + +If a service never appears in any injector, verify it has the `@Injectable()` decorator with `providedIn: 'root'` or is listed in a `providers` array. + +### Logging and tracing injection + +When DevTools isn't enough, use logging to trace injection behavior. + +#### Logging service creation + +Add console logs to service constructors to see when services are created. + +```ts +import {Injectable} from '@angular/core'; + +@Injectable({providedIn: 'root'}) +export class UserClient { + constructor() { + console.log('UserClient created'); + console.trace(); // Shows call stack + } + + getUser() { + return {name: 'Alice'}; + } +} +``` + +When the service is created, you'll see the log message and a stack trace showing where the injection occurred. + +**What to look for:** + +- How many times is the constructor called? (should be once for singletons) +- Where in the code is it being injected? (check the stack trace) +- Is it created at the expected time? (application startup vs lazy) + +#### Checking service availability + +Use optional injection with logging to determine if a service is available. + +```angular-ts +import {Component, inject} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-debug', + template: '

Debug Component

', +}) +export class DebugView { + private userService = inject(UserClient, {optional: true}); + + constructor() { + if (this.userService) { + console.log('UserClient available:', this.userService); + } else { + console.warn('UserClient NOT available'); + console.trace(); // Shows where we tried to inject + } + } +} +``` + +This pattern helps you verify if a service is available without crashing the application. + +#### Logging resolution modifiers + +Test different resolution strategies with logging. + +```angular-ts +import {Component, inject} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-debug', + template: '

Debug Component

', + providers: [UserClient], +}) +export class DebugView { + // Try to get local instance + private localService = inject(UserClient, {self: true, optional: true}); + + // Try to get parent instance + private parentService = inject(UserClient, { + skipSelf: true, + optional: true, + }); + + constructor() { + console.log('Local instance:', this.localService); + console.log('Parent instance:', this.parentService); + console.log('Same instance?', this.localService === this.parentService); + } +} +``` + +This shows you which instances are available at different injector levels. + +### Debugging workflow + +When DI fails, follow this systematic approach: + +**Step 1: Read the error message** + +- Identify the error code (NG0200, NG0203, etc.) +- Read the dependency path +- Note which token failed + +**Step 2: Check the basics** + +- Does the service have `@Injectable()`? +- Is `providedIn` set correctly? +- Are imports correct? +- Is the file included in compilation? + +**Step 3: Verify injection context** + +- Is `inject()` called in a valid context? +- Check for async issues (await, setTimeout, promises) +- Verify timing (not after destroy) + +**Step 4: Use debugging tools** + +- Open Angular DevTools +- Check injector hierarchy +- Add console logs to constructors +- Use optional injection to test availability + +**Step 5: Simplify and isolate** + +- Remove dependencies one by one +- Test in a minimal component +- Check each injector level separately +- Create a reproduction case + +## DI error reference + +This section provides detailed information about specific Angular DI error codes you may encounter. Use this as a reference when you see these errors in your console. + +### NullInjectorError: No provider for [Service] + +**Error code:** None (displayed as `NullInjectorError`) + +This error occurs when Angular cannot find a provider for a token in the injector hierarchy. The error message includes a dependency path showing where the injection was attempted. + +``` +NullInjectorError: No provider for UserClient! + Dependency path: App -> AuthClient -> UserClient +``` + +The dependency path shows that `App` injected `AuthClient`, which tried to inject `UserClient`, but no provider was found. + +#### Missing @Injectable decorator + +The most common cause is forgetting the `@Injectable()` decorator on a service class. + +```ts {avoid, header: 'Missing decorator'} +export class UserClient { + getUser() { + return {name: 'Alice'}; + } +} +``` + +Angular requires the `@Injectable()` decorator to generate the metadata needed for dependency injection. + +```ts {prefer, header: 'Include @Injectable'} +import {Injectable} from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class UserClient { + getUser() { + return {name: 'Alice'}; + } +} +``` + +NOTE: Classes with zero-argument constructors can work without `@Injectable()`, but this is not recommended. Always include the decorator for consistency and to avoid issues when adding dependencies later. + +#### Missing providedIn configuration + +A service may have `@Injectable()` but not specify where it should be provided. + +```ts {avoid, header: 'No providedIn specified'} +import {Injectable} from '@angular/core'; + +@Injectable() +export class UserClient { + getUser() { + return {name: 'Alice'}; + } +} +``` + +Specify `providedIn: 'root'` to make the service available throughout your application. + +```ts {prefer, header: 'Specify providedIn'} +import {Injectable} from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class UserClient { + getUser() { + return {name: 'Alice'}; + } +} +``` + +The `providedIn: 'root'` configuration makes the service available application-wide and enables tree-shaking (the service is removed from the bundle if never injected). + +#### Standalone component missing imports + +In Angular v20+ with standalone components, you must explicitly import or provide dependencies in each component. + +```angular-ts {avoid, header: 'Missing service import'} +import {Component, inject} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-profile', + template: '

User: {{user().name}}

', +}) +export class UserProfile { + private userService = inject(UserClient); // ERROR: No provider + user = this.userService.getUser(); +} +``` + +Ensure the service uses `providedIn: 'root'` or add it to the component's `providers` array. + +```angular-ts {prefer, header: 'Service uses providedIn: root'} +import {Component, inject} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-profile', + template: '

User: {{user().name}}

', +}) +export class UserProfile { + private userService = inject(UserClient); // Works: providedIn: 'root' + user = this.userService.getUser(); +} +``` + +#### Debugging with the dependency path + +The dependency path in the error message shows the chain of injections that led to the failure. + +``` +NullInjectorError: No provider for LoggerStore! + Dependency path: App -> DataStore -> ApiClient -> LoggerStore +``` + +This path tells you: + +1. `App` injected `DataStore` +2. `DataStore` injected `ApiClient` +3. `ApiClient` tried to inject `LoggerStore` +4. No provider for `LoggerStore` was found + +Start your investigation at the end of the chain (`LoggerStore`) and verify it has proper configuration. + +#### Checking provider availability with optional injection + +Use optional injection to check if a provider exists without throwing an error. + +```angular-ts +import {Component, inject} from '@angular/core'; +import {UserClient} from './user-client'; + +@Component({ + selector: 'app-debug', + template: '

Service available: {{serviceAvailable}}

', +}) +export class DebugView { + private userService = inject(UserClient, {optional: true}); + serviceAvailable = this.userService !== null; +} +``` + +Optional injection returns `null` if no provider is found, allowing you to handle the absence gracefully. + +### NG0203: inject() must be called from an injection context + +**Error code:** NG0203 + +This error occurs when you call `inject()` outside of a valid injection context. Angular requires `inject()` to be called synchronously during class construction or factory execution. + +``` +NG0203: inject() must be called from an injection context such as a +constructor, a factory function, a field initializer, or a function +used with `runInInjectionContext`. +``` + +#### Valid injection contexts + +Angular allows `inject()` in these locations: + +1. **Class field initializers** + + ```angular-ts + import {Component, inject} from '@angular/core'; + import {UserClient} from './user-client'; + + @Component({ + selector: 'app-profile', + template: '

User: {{user().name}}

', + }) + export class UserProfile { + private userService = inject(UserClient); // Valid + user = this.userService.getUser(); + } + ``` + +2. **Class constructor** + + ```angular-ts + import {Component, inject} from '@angular/core'; + import {UserClient} from './user-client'; + + @Component({ + selector: 'app-profile', + template: '

User: {{user().name}}

', + }) + export class UserProfile { + private userService: UserClient; + + constructor() { + this.userService = inject(UserClient); // Valid + } + + user = this.userService.getUser(); + } + ``` + +3. **Provider factory functions** + + ```ts + import {inject, InjectionToken} from '@angular/core'; + import {UserClient} from './user-client'; + + export const GREETING = new InjectionToken('greeting', { + factory() { + const userService = inject(UserClient); // Valid + const user = userService.getUser(); + return `Hello, ${user.name}`; + }, + }); + ``` + +4. **Inside runInInjectionContext()** + + ```angular-ts + import {Component, inject, Injector} from '@angular/core'; + import {UserClient} from './user-client'; + + @Component({ + selector: 'app-profile', + template: '', + }) + export class UserProfile { + private injector = inject(Injector); + + loadUser() { + this.injector.runInInjectionContext(() => { + const userService = inject(UserClient); // Valid + console.log(userService.getUser()); + }); + } + } + ``` + +Other injection contexts that `inject()` also works in include: + +- [provideAppInitializer](api/core/provideAppInitializer) +- [provideEnvironmentInitializer](api/core/provideEnvironmentInitializer) +- Functional [route guards](guide/routing/route-guards) +- Functional [data resolvers](guide/routing/data-resolvers) + +#### When this error occurs + +This error occurs when: + +- Calling `inject()` in lifecycle hooks (`ngOnInit`, `ngAfterViewInit`, etc.) +- Calling `inject()` after `await` in async functions +- Calling `inject()` in callbacks (`setTimeout`, `Promise.then()`, etc.) +- Calling `inject()` outside of class construction phase + +See the "Incorrect inject() usage" section for detailed examples and solutions. + +#### Solutions and workarounds + +**Solution 1:** Capture dependencies in field initializers (most common) + +```ts +private userService = inject(UserClient) // Capture at class level +``` + +**Solution 2:** Use `runInInjectionContext()` for callbacks + +```ts +private injector = inject(Injector) + +someCallback() { + this.injector.runInInjectionContext(() => { + const service = inject(MyClient) + }) +} +``` + +**Solution 3:** Pass dependencies as parameters instead of injecting them + +```ts +// Instead of injecting inside a callback +setTimeout(() => { + const service = inject(MyClient) // ERROR +}, 1000) + +// Capture first, then use +private service = inject(MyClient) + +setTimeout(() => { + this.service.doSomething() // Use captured reference +}, 1000) +``` + +### NG0200: Circular dependency detected + +**Error code:** NG0200 + +This error occurs when two or more services depend on each other, creating a circular dependency that Angular cannot resolve. + +``` +NG0200: Circular dependency in DI detected for AuthClient + Dependency path: AuthClient -> UserClient -> AuthClient +``` + +The dependency path shows the cycle: `AuthClient` depends on `UserClient`, which depends back on `AuthClient`. + +#### Understanding the error + +Angular creates service instances by calling their constructors and injecting dependencies. When services depend on each other circularly, Angular cannot determine which to create first. + +#### Common causes + +- Direct circular dependency (Service A → Service B → Service A) +- Indirect circular dependency (Service A → Service B → Service C → Service A) +- Import cycles in module files that also have service dependencies + +#### Resolution strategies + +See the "Circular dependencies" section for detailed examples and solutions: + +1. **Restructure** - Extract shared logic to a third service (recommended) +2. **Use events** - Replace direct dependencies with event-based communication +3. **Lazy injection** - Use `Injector.get()` to defer one dependency (last resort) + +Do NOT use `forwardRef()` for service circular dependencies. It only solves circular imports in component configurations. + +### Other DI error codes + +For detailed explanations and solutions for these errors, see the [Angular error reference](errors): + +| Error Code | Description | +| ----------------------- | ------------------------------------------------------------------------------------------ | +| [NG0204](errors/NG0204) | Can't resolve all parameters - missing `@Injectable()` decorator | +| [NG0205](errors/NG0205) | Injector already destroyed - accessing services after component destruction | +| [NG0207](errors/NG0207) | EnvironmentProviders in wrong context - using `provideHttpClient()` in component providers | + +## Next steps + +When you encounter DI errors, remember to: + +1. Read the error message and dependency path carefully +2. Verify basic configuration (decorators, `providedIn`, imports) +3. Check injection context and timing +4. Use DevTools and logging to investigate +5. Simplify and isolate the problem + +For a deeper understanding of specific topics on dependency injection, check out: + +- [Understanding dependency injection](guide/di) - Core DI concepts and patterns +- [Hierarchical dependency injection](guide/di/hierarchical-dependency-injection) - How the injector hierarchy works +- [Testing with dependency injection](guide/testing) - Using TestBed and mocking dependencies diff --git a/adev-ja/src/content/guide/di/defining-dependency-providers.md b/adev-ja/src/content/guide/di/defining-dependency-providers.md index f685b05ef..84d4684f4 100644 --- a/adev-ja/src/content/guide/di/defining-dependency-providers.md +++ b/adev-ja/src/content/guide/di/defining-dependency-providers.md @@ -66,7 +66,7 @@ export const APP_CONFIG = new InjectionToken('app.config', { selector: 'app-header', template: `

Version: {{ config.version }}

`, }) -export class HeaderComponent { +export class Header { config = inject(APP_CONFIG); // Automatically available } ``` @@ -167,7 +167,7 @@ export class LocalDataStore { providers: [LocalDataStore], template: `...`, }) -export class ExampleComponent { +export class Example { dataStore = inject(LocalDataStore); } ``` @@ -191,7 +191,7 @@ export class DataStore { providers: [DataStore], template: `...`, }) -export class IsolatedComponent { +export class Isolated { dataStore = inject(DataStore); // Component-specific instance } ``` @@ -235,14 +235,14 @@ Think of Angular's dependency injection system as a hash map or dictionary. Each When manually providing dependencies, you typically see this shorthand syntax: ```angular-ts -import { Component } from '@angular/core'; -import { LocalService } from './local-service'; +import {Component} from '@angular/core'; +import {LocalService} from './local-service'; @Component({ selector: 'app-example', - providers: [LocalService] // Service without providedIn + providers: [LocalService], // Service without providedIn }) -export class ExampleComponent { } +export class Example {} ``` This is actually a shorthand for a more detailed provider configuration: @@ -282,16 +282,16 @@ Provider identifiers allow Angular's dependency injection (DI) system to retriev Class name use the imported class directly as the identifier: ```angular-ts -import { Component } from '@angular/core'; -import { LocalService } from './local-service'; +import {Component} from '@angular/core'; +import {LocalService} from './local-service'; @Component({ selector: 'app-example', - providers: [ - { provide: LocalService, useClass: LocalService } - ] + providers: [{provide: LocalService, useClass: LocalService}], }) -export class ExampleComponent { /* ... */ } +export class Example { + /* ... */ +} ``` The class serves as both the identifier and the implementation, which is why Angular provides the shorthand `providers: [LocalService]`. @@ -313,17 +313,15 @@ NOTE: The string `'DataService'` is a description used purely for debugging purp Use the token in your provider configuration: ```angular-ts -import { Component, inject } from '@angular/core'; -import { LocalDataService } from './local-data-service'; -import { DATA_SERVICE_TOKEN } from './tokens'; +import {Component, inject} from '@angular/core'; +import {LocalDataService} from './local-data-service'; +import {DATA_SERVICE_TOKEN} from './tokens'; @Component({ selector: 'app-example', - providers: [ - { provide: DATA_SERVICE_TOKEN, useClass: LocalDataService } - ] + providers: [{provide: DATA_SERVICE_TOKEN, useClass: LocalDataService}], }) -export class ExampleComponent { +export class Example { private dataService = inject(DATA_SERVICE_TOKEN); } ``` @@ -344,7 +342,7 @@ interface DataService { {provide: DataService, useClass: LocalDataService}, // Error! ], }) -export class ExampleComponent { +export class Example { private dataService = inject(DataService); // Error! } @@ -354,7 +352,7 @@ export const DATA_SERVICE_TOKEN = new InjectionToken('DataService') @Component({ providers: [{provide: DATA_SERVICE_TOKEN, useClass: LocalDataService}], }) -export class ExampleComponent { +export class Example { private dataService = inject(DATA_SERVICE_TOKEN); // Works! } ``` @@ -428,7 +426,7 @@ export class EvenBetterLogger extends Logger { {provide: Logger, useClass: EvenBetterLogger}, ], }) -export class ExampleComponent { +export class Example { private logger = inject(Logger); // Gets EvenBetterLogger instance } ``` @@ -485,7 +483,7 @@ bootstrapApplication(AppComponent, { selector: 'app-header', template: `

{{ title }}

`, }) -export class HeaderComponent { +export class Header { private config = inject(APP_CONFIG); title = this.config.appTitle; } @@ -575,7 +573,7 @@ export const apiClientProvider = { selector: 'app-dashboard', providers: [apiClientProvider], }) -export class DashboardComponent { +export class Dashboard { private apiClient = inject(ApiClient); } ``` @@ -628,7 +626,7 @@ Use application-level providers in `bootstrapApplication` when: ```ts // main.ts -bootstrapApplication(AppComponent, { +bootstrapApplication(App, { providers: [ {provide: API_BASE_URL, useValue: 'https://api.example.com'}, {provide: INTERCEPTOR_TOKEN, useClass: AuthInterceptor, multi: true}, @@ -672,20 +670,20 @@ Use component or directive providers when: @Component({ selector: 'app-advanced-form', providers: [ - FormValidationService, // Each form gets its own validator - { provide: FORM_CONFIG, useValue: { strictMode: true } } - ] + FormValidationService, // Each form gets its own validator + {provide: FORM_CONFIG, useValue: {strictMode: true}}, + ], }) -export class AdvancedFormComponent { } +export class AdvancedForm {} // Modal component with isolated state management @Component({ selector: 'app-modal', providers: [ - ModalStateService // Each modal manages its own state - ] + ModalStateService, // Each modal manages its own state + ], }) -export class ModalComponent { } +export class Modal {} ``` **Benefits:** @@ -771,7 +769,7 @@ export function provideAnalytics(config: AnalyticsConfig): Provider[] { // Usage in consumer app // main.ts -bootstrapApplication(AppComponent, { +bootstrapApplication(App, { providers: [ provideAnalytics({ trackingId: 'GA-12345', @@ -881,7 +879,7 @@ export function withRetry(config: RetryConfig): HttpFeature { } // Consumer usage with multiple features -bootstrapApplication(AppComponent, { +bootstrapApplication(App, { providers: [ provideHttpClient( {baseUrl: 'https://api.example.com'}, diff --git a/adev-ja/src/content/guide/drag-drop.md b/adev-ja/src/content/guide/drag-drop.md index c33451438..2e2aebbe3 100644 --- a/adev-ja/src/content/guide/drag-drop.md +++ b/adev-ja/src/content/guide/drag-drop.md @@ -34,8 +34,8 @@ import {Component} from '@angular/core'; import {CdkDrag} from '@angular/cdk/drag-drop'; @Component({ - selector: 'my-custom-component', - templateUrl: 'my-custom-component.html', + selector: 'drag-drop-example', + templateUrl: 'drag-drop-example.html', imports: [CdkDrag], }) export class DragDropExample {} @@ -45,10 +45,10 @@ export class DragDropExample {} You can make any element draggable by adding the `cdkDrag` directive. By default, all draggable elements support free dragging. - - - - + + + + ## Create a list of reorderable draggable elements @@ -57,10 +57,10 @@ Add the `cdkDropList` directive to a parent element to group draggable elements The drag and drop directives don't update your data model. To update the data model, listen to the `cdkDropListDropped` event (once the user finishes dragging) and update the data model manually. - - - - + + + + You can use the `CDK_DROP_LIST` injection token that can be used to reference instances of `cdkDropList`. For more information see the [dependency injection guide](/guide/di) and the [drop list injection token API](api/cdk/drag-drop/CDK_DROP_LIST). @@ -84,27 +84,27 @@ The `cdkDropListConnectedTo` directive works both with a direct reference to ano
``` - - - - + + + + Use the `cdkDropListGroup` directive if you have an unknown number of connected drop lists to set up the connection automatically. Any new `cdkDropList` that is added under a group automatically connects to all other lists. -```html +```angular-html
@for (list of lists; track list) { -
+
}
``` - - - - + + + + You can use the `CDK_DROP_LIST_GROUP` injection token that can be used to reference instances of `cdkDropListGroup`. For more information see the [dependency injection guide](/guide/di) and the [drop list group injection token API](api/cdk/drag-drop/CDK_DROP_LIST_GROUP). @@ -113,23 +113,23 @@ You can use the `CDK_DROP_LIST_GROUP` injection token that can be used to refere By default, a user can move `cdkDrag` elements from one container into another connected container. For more fine-grained control over which elements can be dropped into a container, use `cdkDropListEnterPredicate`. Angular calls the predicate whenever a draggable element enters a new container. Depending on whether the predicate returns true or false, the item may or may not be allowed into the new container. - - - - + + + + ## Attach data You can associate some arbitrary data with both `cdkDrag` and `cdkDropList` by setting `cdkDragData` or `cdkDropListData`, respectively. You can bind to the events fired from both directives that will include this data, allowing you to easily identify the origin of the drag or drop interaction. -```html +```angular-html @for (list of lists; track list) { -
- @for (item of list; track item) { -
- } -
+
+ @for (item of list; track item) { +
+ } +
} ``` @@ -139,10 +139,10 @@ You can associate some arbitrary data with both `cdkDrag` and `cdkDropList` by s By default, the user can drag the entire `cdkDrag` element to move it around. To restrict the user to only be able to do so using a handle element, add the `cdkDragHandle` directive to an element inside of `cdkDrag`. You can have as many `cdkDragHandle` elements as you want. - - - - + + + + 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](/guide/di) and the [drag handle injection token API](api/cdk/drag-drop/CDK_DRAG_HANDLE). @@ -155,10 +155,10 @@ To customize the preview, provide a custom template via `*cdkDragPreview`. The c The cloned element removes its id attribute in order to avoid having multiple elements with the same id on the page. This will cause any CSS that targets that id not to be applied. - - - - + + + + You can use the `CDK_DRAG_PREVIEW` injection token that can be used to reference instances of `cdkDragPreview`. For more information see the [dependency injection guide](/guide/di) and the [drag preview injection token API](api/cdk/drag-drop/CDK_DRAG_PREVIEW). @@ -181,10 +181,10 @@ Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `p While a `cdkDrag` element is being dragged, the directive creates a placeholder element that shows where the element will be placed when dropped. By default, the placeholder is a clone of the element that is being dragged. You can replace the placeholder with a custom one using the `*cdkDragPlaceholder` directive: - - - - + + + + You can use the `CDK_DRAG_PLACEHOLDER` injection token that can be used to reference instances of `cdkDragPlaceholder`. For more information see the [dependency injection guide](/guide/di) and the [drag placeholder injection token API](api/cdk/drag-drop/CDK_DRAG_PLACEHOLDER). @@ -195,10 +195,10 @@ Set the `cdkDragRootElement` attribute if there's an element that you want to ma The attribute accepts a selector and looks up the DOM until it finds an element that matches the selector. If an element is found, it becomes draggable. This is useful for cases such as making a dialog draggable. - - - - + + + + Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `rootElementSelector` within the config. For more information see the [dependency injection guide](/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). @@ -207,20 +207,20 @@ Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `r By default, `cdkDrag` elements not in a `cdkDropList` move from their normal DOM position only when a user manually moves the element. Use the `cdkDragFreeDragPosition` input to explicitly set the element’s position. A common use case for this is restoring a draggable element's position after a user has navigated away and then returned. - - - - + + + + ### Restrict movement within an element To stop the user from being able to drag a `cdkDrag` element outside of another element, pass a CSS selector to the `cdkDragBoundary` attribute. This attribute accepts a selector and looks up the DOM until it finds an element that matches it. If a match is found, the element becomes the boundary that the draggable element can't be dragged outside of `cdkDragBoundary` can also be used when `cdkDrag` is placed inside a `cdkDropList`. - - - - + + + + Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update boundaryElement within the config. For more information see the [dependency injection guide](/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). @@ -229,10 +229,10 @@ Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update bo By default, `cdkDrag` allows free movement in all directions. To restrict dragging to a specific axis, set `cdkDragLockAxis` to either "x" or "y"on `cdkDrag`. To restrict dragging for multiple draggable elements within `cdkDropList`, set `cdkDropListLockAxis` on `cdkDropList` instead. - - - - + + + + Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `lockAxis` within the config. For more information see the [dependency injection guide](/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). @@ -243,10 +243,10 @@ By default when the user puts their pointer down on a `cdkDrag`, the dragging se You can delay the dragging sequence using the `cdkDragStartDelay` input. The input waits for the user to hold down their pointer for the specified number of milliseconds before dragging the element. - - - - + + + + Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update dragStartDelay within the config. For more information see the [dependency injection guide](/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). @@ -255,10 +255,10 @@ Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update dr If you want to disable dragging for a particular drag item, set the `cdkDragDisabled` input on a `cdkDrag` item to true or false. You can disable an entire list using the `cdkDropListDisabled` input on a `cdkDropList`. It is also possible to disable a specific handle via `cdkDragHandleDisabled` on `cdkDragHandle`. - - - - + + + + Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `draggingDisabled` within the config. For more information see the [dependency injection guide](/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). @@ -269,10 +269,10 @@ Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `d By default, the `cdkDropList` directive assumes lists are vertical. This can be changed by setting the `cdkDropListOrientation` property to horizontal. - - - - + + + + Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `listOrientation` within the config. For more information see the [dependency injection guide](/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). @@ -283,30 +283,30 @@ By default, the `cdkDropList` sorts the draggable elements by moving them around If you have a sortable list that needs to wrap onto new lines, you can set `cdkDropListOrientation` attribute to `mixed`. This causes the list to use a different strategy of sorting the elements which involves moving them in the DOM. However the list can no longer animate the sorting action . - - - - + + + + ### Selective sorting By default, `cdkDrag` elements are sorted into any position inside of a `cdkDropList`. To change this behavior, set the `cdkDropListSortPredicate` attribute which takes in a function. The predicate function is called whenever a draggable element is about to be moved into a new index within the drop list. If the predicate returns true, the item will be moved into the new index, otherwise it will keep its current position. - - - - + + + + ### 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. - - - - + + + + Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update sortingDisabled within the config. For more information see the [dependency injection guide](/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). @@ -319,10 +319,10 @@ To enable copying, you can set the `cdkDropListHasAnchor` input. This tells the Combining `cdkDropListHasAnchor` with `cdkDropListSortingDisabled` makes it possible to construct a list from which a user can copy items without being able to reorder the source list (e.g. a product list and a shopping cart). - - - - + + + + ## Customize animations diff --git a/adev-ja/src/content/guide/forms/signals/async-operations.md b/adev-ja/src/content/guide/forms/signals/async-operations.md new file mode 100644 index 000000000..ec4a837c2 --- /dev/null +++ b/adev-ja/src/content/guide/forms/signals/async-operations.md @@ -0,0 +1,552 @@ +# Async operations + +Some validation requires data from external sources like backend APIs or third-party services. Signal Forms provides two functions for asynchronous validation: `validateHttp()` for HTTP-based validation and `validateAsync()` for custom resource-based validation. + +## When to use async validation + +Use async validation when your validation logic requires external data. Some common examples include: + +- **Uniqueness checks** - Verify usernames or emails don't already exist +- **Database lookups** - Check values against server-side data +- **External API validation** - Validate addresses, tax IDs, or other data with third-party services +- **Server-side business rules** - Apply validation rules that only the server can verify + +Don't use async validation for checks you can perform synchronously on the client. Use synchronous validation rules like `pattern()`, `email()`, or `validate()` for format validation and static rules. + +## How async validation works + +Async validation runs only after all synchronous validation passes. While the validation executes, the field's `pending()` signal returns `true`. The validation can target errors to specific fields, and pending requests cancel automatically when field values change. + +Here's an example checking username availability: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, validateHttp, FormField} from '@angular/forms/signals'; + +@Component({ + selector: 'app-registration', + imports: [FormField], + template: ` +
+ + + @if (registrationForm.username().pending()) { + Checking availability... + } + @if (registrationForm.username().invalid()) { + @for (error of registrationForm.username().errors(); track $index) { + {{ error.message }} + } + } +
+ `, +}) +export class Registration { + registrationModel = signal({username: ''}); + + registrationForm = form(this.registrationModel, (schemaPath) => { + validateHttp(schemaPath.username, { + request: ({value}) => { + const username = value(); + return username ? `/api/users/check?username=${username}` : undefined; + }, + onSuccess: (response) => { + return response.available + ? null + : { + kind: 'usernameTaken', + message: 'Username is already taken', + }; + }, + onError: (error) => { + console.error('Validation request failed:', error); + return { + kind: 'serverError', + message: 'Could not verify username availability', + }; + }, + }); + }); +} +``` + +The validation flow works like this: + +1. User types a value +2. Synchronous validation rules run first +3. If synchronous validation fails, async validation doesn't run +4. If synchronous validation passes, async validation starts and `pending()` becomes `true` +5. The request completes and `pending()` becomes `false` +6. Errors update based on the response + +## HTTP validation with validateHttp() + +The `validateHttp()` function provides the most common form of async validation. Use it when you need to validate against a REST API or any HTTP endpoint. + +### Request function + +The `request` function returns either a URL string or an `HttpResourceRequest` object. Return `undefined` to skip the validation: + +```ts +import {Component, signal} from '@angular/core'; +import {form, validateHttp, FormField} from '@angular/forms/signals'; + +@Component({ + selector: 'app-registration', + imports: [FormField], + template: `...`, +}) +export class Registration { + registrationModel = signal({username: ''}); + + // Cache usernames that passed validation + private validatedUsernames = new Set(); + + registrationForm = form(this.registrationModel, (schemaPath) => { + validateHttp(schemaPath.username, { + request: ({value}) => { + const username = value(); + // Skip HTTP request if already validated + if (this.validatedUsernames.has(username)) return undefined; + + return `/api/users/check?username=${username}`; + }, + onSuccess: (response, {value}) => { + if (response.available) { + // Cache successful validations + this.validatedUsernames.add(value()); + return null; + } + return { + kind: 'usernameTaken', + message: 'Username is already taken', + }; + }, + onError: () => ({ + kind: 'serverError', + message: 'Could not verify username', + }), + }); + }); +} +``` + +For POST requests or custom headers, return an `HttpResourceRequest` object: + +```ts +request: ({value}) => ({ + url: '/api/validate', + method: 'POST', + body: {username: value()}, +}) // prettier-ignore +``` + +### Success and error handlers + +The `onSuccess` function receives the HTTP response and returns validation errors or `undefined` for valid values: + +```ts +onSuccess: (response) => { + if (response.valid) return undefined; + + return { + kind: 'invalid', + message: response.message || 'Validation failed', + }; +} // prettier-ignore +``` + +Return multiple errors when needed: + +```ts +onSuccess: (response) => { + const errors = []; + if (response.usernameTaken) { + errors.push({ + kind: 'usernameTaken', + message: 'Username taken', + }); + } + if (response.profanity) { + errors.push({ + kind: 'profanity', + message: 'Username contains inappropriate content', + }); + } + return errors.length > 0 ? errors : undefined; +} // prettier-ignore +``` + +The `onError` function handles request failures like network errors or HTTP errors: + +```ts +onError: (error) => { + console.error('Validation request failed:', error); + return { + kind: 'serverError', + message: 'Could not verify. Please try again later.', + }; +} // prettier-ignore +``` + +### HTTP options + +Customize the HTTP request with the `options` parameter: + +```ts +import {HttpHeaders} from '@angular/common/http'; + +validateHttp(schemaPath.field, { + request: ({value}) => `/api/validate?value=${value()}`, + options: { + headers: new HttpHeaders({ + Authorization: 'Bearer token', + }), + timeout: 5000, + }, + onSuccess: (response) => + response.valid + ? null + : { + kind: 'invalid', + message: 'Invalid value', + }, + onError: () => ({ + kind: 'requestFailed', + message: 'Unable to reach server to validate.', + }), +}); +``` + +TIP: See the [httpResource API documentation](api/common/http/httpResource) for all available options. + +## Custom async validation with validateAsync() + +Most applications should use `validateHttp()` for async validation. It handles HTTP requests with minimal configuration and covers the majority of use cases. + +`validateAsync()` is a lower-level API that exposes Angular's resource primitive directly. It offers complete control but requires more code and familiarity with Angular's resource API. + +Consider `validateAsync()` only when `validateHttp()` can't meet your needs. Some examples include: + +- **Non-HTTP validation** - WebSocket connections, IndexedDB lookups, or Web Worker computations +- **Custom caching strategies** - Application-specific caching beyond simple memoization +- **Complex retry logic** - Custom backoff strategies or conditional retries +- **Direct resource access** - When you need the full resource lifecycle + +### Creating a custom validation rule + +The `validateAsync()` function requires four properties: `params`, `factory`, `onSuccess`, and `onError`. The `params` function returns the parameters for your resource, while `factory` creates the resource: + +```ts +import {Component, inject, signal, resource, Signal} from '@angular/core'; +import {form, validateAsync, FormField} from '@angular/forms/signals'; +import {UsernameValidator} from './username-validator'; + +@Component({ + selector: 'app-registration', + imports: [FormField], + template: `...`, +}) +export class Registration { + registrationModel = signal({username: ''}); + + private usernameValidator = inject(UsernameValidator); + private cache = new Map(); + + // Custom resource factory with caching + createUsernameResource = (usernameSignal: Signal) => { + return resource({ + params: () => usernameSignal(), + loader: async ({params: username}) => { + if (!username) return undefined; + + // Check cache first + const cached = this.cache.get(username); + if (cached !== undefined) return cached; + + // Use injected service for validation + const result = await this.usernameValidator.checkAvailability(username); + + // Cache result + this.cache.set(username, result); + return result; + }, + }); + }; + + registrationForm = form(this.registrationModel, (schemaPath) => { + validateAsync(schemaPath.username, { + params: ({value}) => { + const username = value(); + return username.length >= 3 ? username : undefined; + }, + factory: this.createUsernameResource, + onSuccess: (result) => { + return result?.available + ? null + : { + kind: 'usernameTaken', + message: 'Username taken', + }; + }, + onError: (error) => { + console.error('Validation failed:', error); + return { + kind: 'serverError', + message: 'Could not verify username', + }; + }, + }); + }); +} +``` + +The `params` function runs on every value change. Return `undefined` to skip validation. The `factory` function runs once during setup and receives params as a signal. The resource updates automatically when params change. + +### Using Observable-based services + +If your application has existing services that return Observables, use `rxResource` from `@angular/core/rxjs-interop`: + +```ts +import {Component, inject, signal, Signal} from '@angular/core'; +import {rxResource} from '@angular/core/rxjs-interop'; +import {form, validateAsync, FormField} from '@angular/forms/signals'; +import {UsernameService} from './username-service'; + +@Component({ + selector: 'app-registration', + imports: [FormField], + template: `...`, +}) +export class Registration { + registrationModel = signal({username: ''}); + + private usernameService = inject(UsernameService); + + private createUsernameResource = (usernameSignal: Signal) => { + return rxResource({ + request: () => usernameSignal(), + stream: ({request: username}) => this.usernameService.checkUsername(username), + }); + }; + + registrationForm = form(this.registrationModel, (schemaPath) => { + validateAsync(schemaPath.username, { + params: ({value}) => value() || undefined, + factory: this.createUsernameResource, + onSuccess: (result) => + result?.available ? null : {kind: 'usernameTaken', message: 'Username taken'}, + onError: () => ({ + kind: 'serverError', + message: 'Could not verify username', + }), + }); + }); +} +``` + +The `rxResource` function works directly with Observables and handles subscription cleanup automatically when the field value changes. + +## Understanding pending state + +When async validation runs, the field's `pending()` signal returns `true`. During this time: + +- `valid()` returns `false` +- `invalid()` returns `false` +- `errors()` returns an empty array +- `submit()` waits for validation to complete + +Show the pending state in your template to provide feedback: + +```angular-html + + +@if (loginForm.username().pending()) { + Checking availability... +} + +@if (loginForm.username().touched() && loginForm.username().invalid()) { + @for (error of loginForm.username().errors(); track $index) { + {{ error.message }} + } +} +``` + +Disable form submission while validation is pending: + +```angular-html + +``` + +TIP: See the [Field State Management guide](guide/forms/signals/field-state-management) for more patterns using `pending()`, `valid()`, and `invalid()` signals. + +### Validation execution order + +Async validation only runs after synchronous validation passes. This prevents unnecessary server requests for invalid input: + +```ts +import {form, required, minLength, validateHttp} from '@angular/forms/signals'; + +form(model, (schemaPath) => { + // 1. These synchronous validation rules run first + required(schemaPath.username); + minLength(schemaPath.username, 3); + + // 2. This async validation rule only runs if synchronous validation passes + validateHttp(schemaPath.username, { + request: ({value}) => `/api/check?username=${value()}`, + onSuccess: (result) => + result.valid + ? null + : { + kind: 'usernameTaken', + message: 'Username taken', + }, + onError: () => ({ + kind: 'serverError', + message: 'Validation failed', + }), + }); +}); +``` + +This execution order improves performance by reducing server load and catching format errors instantly. + +### Request cancellation + +When a field value changes, Signal Forms automatically cancels any pending async validation request for that field. This prevents race conditions and ensures validation always reflects the current value. You don't need to implement cancellation logic yourself. + +## Best practices + +### Combine with synchronous validation + +Always validate format before making async requests. This catches errors instantly and prevents unnecessary server requests: + +```ts +import {form, required, email, validateHttp} from '@angular/forms/signals'; + +form(model, (schemaPath) => { + // Validate format first + required(schemaPath.email); + email(schemaPath.email); + + // Then check availability + validateHttp(schemaPath.email, { + request: ({value}) => `/api/emails/check?email=${value()}`, + onSuccess: (result) => + result.available + ? null + : { + kind: 'emailInUse', + message: 'Email already in use', + }, + onError: () => ({ + kind: 'serverError', + message: 'Could not verify email', + }), + }); +}); +``` + +### Skip validation when appropriate + +Return `undefined` from the `request` function to skip validation. Use this to avoid validating empty fields or values that don't meet minimum requirements: + +```ts +import {validateHttp} from '@angular/forms/signals'; + +validateHttp(schemaPath.username, { + request: ({value}) => { + const username = value(); + // Skip validation for empty or short usernames + if (!username || username.length < 3) return undefined; + + return `/api/users/check?username=${username}`; + }, + onSuccess: (result) => + result.valid + ? null + : { + kind: 'usernameTaken', + message: 'Username taken', + }, + onError: () => ({ + kind: 'serverError', + message: 'Validation failed', + }), +}); +``` + +### Handle errors gracefully + +Provide clear, user-friendly error messages. Log technical details for debugging but show simple messages to users: + +```ts +import {validateHttp} from '@angular/forms/signals'; + +validateHttp(schemaPath.field, { + request: ({value}) => `/api/validate?field=${value()}`, + onSuccess: (result) => { + if (result.valid) return null; + // Use server message when available + return { + kind: 'serverError', + message: result.message || 'Validation failed', + }; + }, + onError: (error) => { + // Log for debugging + console.error('Validation request failed:', error); + + // Show user-friendly message + return { + kind: 'serverError', + message: 'Unable to validate. Please try again later.', + }; + }, +}); +``` + +### Show clear feedback + +Use the `pending()` signal to show when validation is happening. This helps users understand delays and provides better perceived performance: + +```angular-html +@if (field().pending()) { + + + Checking... + +} +@if (field().valid() && !field().pending()) { + Available +} +@if (field().invalid()) { + {{ field().errors()[0]?.message }} +} +``` + +## Next steps + +This guide covered async validation with `validateHttp()` and `validateAsync()`. Related guides explore other aspects of Signal Forms: + + + + + + +For detailed API documentation, see: + +- [`validateHttp()`](api/forms/signals/validateHttp) - HTTP-based async validation +- [`validateAsync()`](api/forms/signals/validateAsync) - Custom resource-based async validation +- [`httpResource()`](api/common/http/httpResource) - Angular's HTTP resource API +- [`resource()`](api/core/resource) - Angular's resource primitive diff --git a/adev-ja/src/content/guide/forms/signals/designing-your-form-model.md b/adev-ja/src/content/guide/forms/signals/designing-your-form-model.md index 007f092d3..750d8ac17 100644 --- a/adev-ja/src/content/guide/forms/signals/designing-your-form-model.md +++ b/adev-ja/src/content/guide/forms/signals/designing-your-form-model.md @@ -109,7 +109,7 @@ interface BeverageOrderFormModel { ### Avoid `undefined` -A form model model must not contain `undefined` values or properties. In Signal Forms the structure of the form is derived from the structure of the model, and `undefined` signifies the _absence of a field_, rather than a field with an empty value. This means you must also avoid optional fields (e.g., `{property?: string}`), as they implicitly allow `undefined`. +A form model must not contain `undefined` values or properties. In Signal Forms the structure of the form is derived from the structure of the model, and `undefined` signifies the _absence of a field_, rather than a field with an empty value. This means you must also avoid optional fields (e.g., `{property?: string}`), as they implicitly allow `undefined`. To represent a property with an empty value in your form model, use a value that the UI control understands to mean "empty" (e.g. `""` for a ``). If you're designing a custom UI control, `null` often works as a good value to signify "empty". @@ -161,12 +161,12 @@ createAccountForm = form( ); ``` -Using this representation, all of the subfields we need now exist, and we can bind them using the `[field]` directive in our template. +Using this representation, all of the subfields we need now exist, and we can bind them using the `[formField]` directive in our template. ```html -First: Last: - Username: - +First: Last: + Username: + ``` #### Fields that are conditionally hidden or unavailable @@ -289,8 +289,8 @@ interface UserProfileFormModel { In the template, we bind the `location` field directly to our custom control: ```html -Username: Location: - +Username: Location: + ``` Here, `` consumes and produces the entire `Location` object (or `null`), and doesn't access `userForm.location.lat` or `userForm.location.lng`. Therefore, `location` can safely have a dynamic shape without violating the principles of model-driven forms. @@ -409,7 +409,7 @@ class MyForm { } ``` -The examples above show a pure conversion from the form model to the domain model. However, it is perfectly acceptable to consider the full form state in addition to just the form model value. For example, to save bytes me might want to only send partial updates to the server based on what the user changed. In this case our conversion function could be designed to take the entire form state and return a sparse domain model based on the form's values and dirtiness. +The examples above show a pure conversion from the form model to the domain model. However, it is perfectly acceptable to consider the full form state in addition to just the form model value. For example, to save bytes we might want to only send partial updates to the server based on what the user changed. In this case our conversion function could be designed to take the entire form state and return a sparse domain model based on the form's values and dirtiness. ```ts type Sparse = T extends object ? { diff --git a/adev-ja/src/content/guide/forms/signals/form-logic.md b/adev-ja/src/content/guide/forms/signals/form-logic.md new file mode 100644 index 000000000..5aa44718a --- /dev/null +++ b/adev-ja/src/content/guide/forms/signals/form-logic.md @@ -0,0 +1,866 @@ +# Adding form logic + +Signal Forms allow you to add logic to your form using schemas. Validation logic is covered in the [Validation guide](guide/forms/signals/validation), and this guide discusses other rules available in schemas. You can disable fields conditionally, hide them based on other values, make them readonly, debounce user input, and attach metadata for custom controls. + +This guide shows you how to use rules like `disabled()`, `hidden()`, `readonly()`, `debounce()`, and `metadata()` to control field behavior. + +## When to add form logic + +Use rules when field behavior depends on other field values or needs to update reactively. For example: + +- A coupon code field that's disabled when the order total is too low +- An address field that's hidden unless shipping is required +- A search field that debounces to reduce API calls + +## How rules work + +Rules bind reactive logic to specific fields in your form. Most rules accept a reactive logic function as an optional argument. The reactive logic function automatically recomputes whenever the signals it references change, just like a `computed`. + +```ts +const orderForm = form(this.orderModel, (schemaPath) => { + disabled(schemaPath.couponCode, ({valueOf}) => valueOf(schemaPath.total) < 50); + //~~~~~~ ~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //rule path reactive logic function +}); +``` + +Reactive logic functions receive a `FieldContext` object that provides access to field values and state through helper functions like `valueOf()` and `stateOf()`. It is often destructured to access these helpers directly. + +NOTE: The schema callback parameter (`schemaPath` in these examples) is a `SchemaPathTree` object that provides paths to all fields in your form. You can name this parameter anything you like. + +For complete details on `FieldContext` properties and methods, see the [Validation guide](guide/forms/signals/validation). + +## Prevent field updates with `disabled()` + +The `disabled()` rule configures a field's disabled state. + +It works with the `[formField]` directive to automatically bind the `disabled` attribute based on the field's state, so you don't need to manually add `[disabled]="yourForm.fieldName().disabled()"` to your template. + +NOTE: Disabled fields skip validation - they don't participate in form validation checks. The field's value is preserved but not validated. For details on validation behavior, see the [Validation guide](guide/forms/signals/validation). + +### Always disabled + +To disable a field permanently, call `disabled()` with just the field path: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, disabled} from '@angular/forms/signals'; + +@Component({ + selector: 'app-settings', + imports: [FormField], + template: ` + + `, +}) +export class Settings { + settingsModel = signal({ + systemId: 'SYS-12345', + userName: '', + }); + + settingsForm = form(this.settingsModel, (schemaPath) => { + disabled(schemaPath.systemId); + }); +} +``` + +### Conditional disabling + +To disable a field based on conditions, provide a reactive logic function that returns `true` (disabled) or `false` (enabled): + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, disabled} from '@angular/forms/signals'; + +@Component({ + selector: 'app-order', + imports: [FormField], + template: ` + + + + `, +}) +export class Order { + orderModel = signal({ + total: 25, + couponCode: '', + }); + + orderForm = form(this.orderModel, (schemaPath) => { + disabled(schemaPath.couponCode, ({valueOf}) => valueOf(schemaPath.total) < 50); + }); +} +``` + +In this example, when the order total is less than $50, the coupon code field is disabled. + +### Disabled reasons + +When you disable a field, provide user-facing explanations by returning a string instead of `true`: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, disabled} from '@angular/forms/signals'; + +@Component({ + selector: 'app-order', + imports: [FormField], + template: ` + + + + + @if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ } +
+ } + `, +}) +export class Order { + orderModel = signal({ + total: 25, + couponCode: '', + }); + + orderForm = form(this.orderModel, (schemaPath) => { + disabled(schemaPath.couponCode, ({valueOf}) => + valueOf(schemaPath.total) < 50 ? 'Order must be $50 or more to use a coupon' : false, + ); + }); +} +``` + +The reactive logic function returns: + +- A **string** to disable the field with a reason +- `false` to enable the field (not just any falsy value - use `false` explicitly) + +Access the reasons through the `disabledReasons()` signal on the field state. Each reason has a `message` property containing the string you returned. + +#### Multiple disabled reasons + +You can also call `disabled()` multiple times on the same field, and all of the returned reasons accumulate: + +```angular-ts +orderForm = form(this.orderModel, (schemaPath) => { + disabled(schemaPath.promoCode, ({valueOf}) => + !valueOf(schemaPath.hasAccount) ? 'You must have an account to use promo codes' : false, + ); + disabled(schemaPath.promoCode, ({valueOf}) => + valueOf(schemaPath.total) < 25 ? 'Order must be at least $25' : false, + ); +}); +``` + +If both conditions are true, the field shows both disabled reasons. This pattern is useful for complex availability rules that you want to keep separate. + +## Configuring `hidden()` state on fields + +The `hidden()` rule configures a field's hidden state. However, this only sets a programmatic state. **You control whether the field appears in the UI**. + +IMPORTANT: Unlike `disabled` and `readonly`, there is no native DOM property for `hidden` state. The `[formField]` directive does not apply a `hidden` attribute to elements. You must use `@if` or CSS in your template to conditionally render fields based on the `hidden()` state. + +NOTE: Like disabled fields, hidden fields also skip validation. See the [Validation guide](guide/forms/signals/validation) for details. + +### Basic field hiding + +Use `hidden()` with a reactive logic function that returns `true` (hidden) or `false` (visible): + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, hidden} from '@angular/forms/signals'; + +@Component({ + selector: 'app-profile', + imports: [FormField], + template: ` + + + @if (!profileForm.publicUrl().hidden()) { + + } + `, +}) +export class Profile { + profileModel = signal({ + isPublic: false, + publicUrl: '', + }); + + profileForm = form(this.profileModel, (schemaPath) => { + hidden(schemaPath.publicUrl, ({valueOf}) => !valueOf(schemaPath.isPublic)); + }); +} +``` + +## Display uneditable fields with `readonly()` + +The `readonly()` rule prevents users from updating a field. The `[FormField]` directive automatically binds this state to the HTML `readonly` attribute, which prevents editing while still allowing users to focus and select text. + +NOTE: Readonly fields skip [validation](guide/forms/signals/validation). + +### Always readonly + +To make a field permanently readonly, call `readonly()` with just the field path: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, readonly} from '@angular/forms/signals'; + +@Component({ + selector: 'app-account', + imports: [FormField], + template: ` + + + + `, +}) +export class Account { + accountModel = signal({ + username: 'johndoe', + email: 'john@example.com', + }); + + accountForm = form(this.accountModel, (schemaPath) => { + readonly(schemaPath.username); + }); +} +``` + +The `[FormField]` directive automatically binds the `readonly` attribute based on the field's state. + +### Conditional readonly + +To make a field readonly based on conditions, provide a reactive logic function: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, readonly} from '@angular/forms/signals'; + +@Component({ + selector: 'app-document', + imports: [FormField], + template: ` + + + + `, +}) +export class Document { + documentModel = signal({ + isLocked: false, + title: 'Untitled', + }); + + documentForm = form(this.documentModel, (schemaPath) => { + readonly(schemaPath.title, ({valueOf}) => valueOf(schemaPath.isLocked)); + }); +} +``` + +When `isLocked` is true, the title field becomes readonly. + +## Choose between hidden, disabled, and readonly + +These three configuration functions control field availability in different ways: + +Choose `hidden()` when the field: + +- Should not appear in the UI at all +- Is irrelevant to the current form state +- Example: Shipping address fields when "same as billing" is checked + +Choose `disabled()` when the field: + +- Should be visible but not editable +- Needs to show why it's unavailable (using disabled reasons) +- Should be excluded from HTML form submission +- Example: Submit button disabled until form is valid, approval fields disabled for non-admin users + +Choose `readonly()` when the field: + +- Should be visible but not editable +- Contains data users need to see, select, or copy +- Should be included in HTML form submission +- Example: Order confirmation number, system-generated reference codes + +All three skip validation and prevent user editing while active. The key differences: + +| Feature | `hidden()` | `disabled()` | `readonly()` | +| -------------------------------- | ---------- | ------------ | ------------ | +| Visible in UI | No | Yes | Yes | +| Users can focus/select | No | No | Yes | +| Included in HTML form submission | No | No | Yes | + +## Delay input operations with `debounce()` + +The `debounce()` rule delays updating the form model. This is useful for performance optimization and reducing unnecessary operations during rapid input. + +### What debouncing does + +Without debouncing, every keystroke immediately updates the form model. This can trigger: + +- Expensive computed signals that recalculate on every change +- Validation checks after each character +- API calls or other side effects tied to the model value + +Debouncing delays these updates and reduces unnecessary work. + +### Basic debouncing + +You can debounce a field by specifying a delay in milliseconds: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, debounce} from '@angular/forms/signals'; + +@Component({ + selector: 'app-search', + imports: [FormField], + template: ` + + +

Searching for: {{ searchForm.query().value() }}

+ `, +}) +export class Search { + searchModel = signal({ + query: '', + }); + + searchForm = form(this.searchModel, (schemaPath) => { + debounce(schemaPath.query, 300); + }); +} +``` + +With a 300ms debounce: + +- User types in the input field +- Form model updates only after 300ms of typing inactivity +- If user keeps typing, the timer resets with each keystroke +- Once user pauses for 300ms, the model updates with the final value + +### Timing guarantees + +The `debounce()` function ensures users don't lose data through these mechanisms: + +- **When marked as touched:** The value syncs immediately, aborting any pending debounce delay. This happens when the field loses focus (blur) or when explicitly marked as touched. +- **On form submission:** All fields are marked as touched before validation, which ensures all debounced values sync immediately. + +This means users can type quickly, tab away, or submit the form without waiting for debounce delays to expire. + +### Custom debounce logic + +For advanced control, provide a debouncer function that controls when to synchronize the value. This function is called every time the control value is updated and can return either `undefined` to synchronize immediately, or a Promise that prevents synchronization until it resolves: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, debounce} from '@angular/forms/signals'; + +@Component({ + selector: 'app-search', + imports: [FormField], + template: ` + + `, +}) +export class Search { + searchModel = signal({ + query: '', + }); + + searchForm = form(this.searchModel, (schemaPath) => { + debounce(schemaPath.query, () => { + // Return a promise that resolves after 500ms + return new Promise((resolve) => { + setTimeout(() => resolve(), 500); + }); + }); + }); +} +``` + +The debouncer function can return: + +- `undefined` to synchronize the value immediately +- A `Promise` that prevents synchronization until it resolves + +Use cases for custom debounce logic: + +- Implementing custom timing logic beyond simple delays +- Coordinating synchronization with external events +- Conditional debouncing based on application state + +### When to use debouncing + +Debouncing is most useful when: + +- You have expensive computed signals that depend on the field value +- The field triggers API calls or other side effects +- You want to reduce validation overhead during rapid typing +- Performance profiling shows model updates are causing slowdowns + +Don't use debouncing if: + +- The field needs immediate updates for good UX (such as calculator inputs) +- The performance benefit is negligible +- Users expect real-time feedback + +## Associate data with a field using `metadata()` + +Metadata allows you to attach computed information to fields that can be read by [custom controls](guide/forms/signals/custom-controls) or form logic. Common use cases include HTML input attributes (min, max, maxlength, pattern), custom UI hints (placeholder text, help text), and accessibility information. + +### Pre-defined metadata keys + +Signal Forms provides six pre-defined metadata keys that validation rules automatically populate: + +- `REQUIRED` - Whether the field is required (`boolean`) +- `MIN` - Minimum numeric value (`number | undefined`) +- `MAX` - Maximum numeric value (`number | undefined`) +- `MIN_LENGTH` - Minimum string/array length (`number | undefined`) +- `MAX_LENGTH` - Maximum string/array length (`number | undefined`) +- `PATTERN` - Regular expression pattern (`RegExp[]` - array to support multiple patterns) + +When you use validation rules like `required()` or `min()`, they automatically set the corresponding metadata. The `metadata()` function provides a way to publish additional data associated with a field. + +### Reading pre-defined metadata + +The `[FormField]` directive automatically binds built-in metadata to HTML attributes. You can also read metadata directly using the built-in accessors on field state: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, required, min, max} from '@angular/forms/signals'; + +@Component({ + selector: 'app-age', + imports: [FormField], + template: ` + + + @if (ageForm.age().required()) { + * + } + `, +}) +export class Age { + ageModel = signal({ + age: 0, + }); + + ageForm = form(this.ageModel, (schemaPath) => { + required(schemaPath.age); + min(schemaPath.age, 18); + max(schemaPath.age, 120); + }); +} +``` + +The `[formField]` directive automatically binds `required`, `min`, and `max` attributes to the input. You can read these values using `field().required()`, `field().min()`, and `field().max()` for display or logic purposes. + +### Setting metadata manually + +Use the `metadata()` function to set metadata values when validation rules don't automatically set them. For built-in metadata like `MIN` and `MAX`, prefer using the validation rules: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, min, max, validate} from '@angular/forms/signals'; + +@Component({ + selector: 'app-custom', + imports: [formField], + template: ` `, +}) +export class Custom { + customModel = signal({score: 0}); + + customForm = form(this.customModel, (schemaPath) => { + // Use built-in validation rules - they automatically set metadata + min(schemaPath.score, 0); + max(schemaPath.score, 100); + + // Add custom validation logic if needed + validate(schemaPath.score, ({value}) => { + const score = value(); + // Custom validation beyond min/max (e.g., must be multiple of 5) + if (score % 5 !== 0) { + return {kind: 'increment', message: 'Score must be a multiple of 5'}; + } + return null; + }); + }); +} +``` + +### Creating custom metadata keys + +Create your own metadata keys for application-specific information: + +```angular-ts +import {createMetadataKey, metadata} from '@angular/forms/signals'; + +// Define at module level (not inside components) +export const PLACEHOLDER = createMetadataKey(); +export const HELP_TEXT = createMetadataKey(); + +// Use in schema +form(model, (schemaPath) => { + metadata(schemaPath.email, PLACEHOLDER, () => 'user@example.com'); + metadata(schemaPath.email, HELP_TEXT, () => 'We will never share your email'); +}); + +// Read in component +const placeholderText = myForm.email().metadata(PLACEHOLDER); +const helpText = myForm.email().metadata(HELP_TEXT); +``` + +By default, custom metadata keys use a "last write wins" strategy - if you call `metadata()` multiple times with the same key, only the last value is kept. + +**Important:** Always define metadata keys at module level, never inside components. Metadata keys rely on object identity, and recreating them loses that identity. + +### Accumulating metadata with reducers + +By default, calling `metadata()` multiple times with the same key uses "last write wins" - only the final value is kept. To accumulate values instead, pass a reducer to `createMetadataKey()`: + +```angular-ts +import {createMetadataKey, metadata, MetadataReducer} from '@angular/forms/signals'; + +// Create a key that accumulates values into an array +export const HINTS = createMetadataKey(MetadataReducer.list()); + +// Multiple calls accumulate values +form(model, (schemaPath) => { + metadata(schemaPath.password, HINTS, () => 'At least 8 characters'); + metadata(schemaPath.password, HINTS, () => 'Include a number'); + metadata(schemaPath.password, HINTS, () => 'Include a special character'); +}); + +// Result: Signal containing the accumulated array +const passwordHints = passwordForm.password().metadata(HINTS)(); +// ['At least 8 characters', 'Include a number', 'Include a special character'] +``` + +Angular provides built-in reducers through `MetadataReducer`: + +- `MetadataReducer.list()` - Accumulates values into an array +- `MetadataReducer.min()` - Keeps the minimum value +- `MetadataReducer.max()` - Keeps the maximum value +- `MetadataReducer.or()` - Logical OR of boolean values +- `MetadataReducer.and()` - Logical AND of boolean values + +### Managed metadata keys + +Use `createManagedMetadataKey()` when you need to compute a new value from the accumulated result. The transform function receives a signal of the reduced value and returns the computed result: + +```angular-ts +import {createManagedMetadataKey, metadata, MetadataReducer} from '@angular/forms/signals'; + +// Accumulate hints and compute additional data from the result +export const HINTS = createManagedMetadataKey( + (signal) => + computed(() => { + const hints = signal(); + return { + messages: hints, + count: hints?.length ?? 0, + }; + }), + MetadataReducer.list(), +); + +// Multiple calls accumulate values +form(model, (schemaPath) => { + metadata(schemaPath.password, HINTS, () => 'At least 8 characters'); + metadata(schemaPath.password, HINTS, () => 'Include a number'); + metadata(schemaPath.password, HINTS, () => 'Include a special character'); +}); + +// Result: Signal with transformed value +const passwordHints = passwordForm.password().metadata(HINTS)(); +// { messages: ['At least 8 characters', 'Include a number', 'Include a special character'], count: 3 } +``` + +The managed metadata key takes two arguments: + +1. **Transform function** - Computes a new value from the accumulated result (receives a signal of the reduced value) +2. **Reducer** - Determines how values accumulate (optional - defaults to "last write wins") + +### Reactive metadata + +Make metadata reactive to other field values: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, max} from '@angular/forms/signals'; + +@Component({ + selector: 'app-inventory', + imports: [formField], + template: ` + + + + `, +}) +export class Inventory { + inventoryModel = signal({ + item: 'widget', + quantity: 0, + }); + + inventoryForm = form(this.inventoryModel, (schemaPath) => { + max(schemaPath.quantity, ({valueOf}) => { + const item = valueOf(schemaPath.item); + return item === 'widget' ? 100 : 50; + }); + }); +} +``` + +The `max()` validation rule sets the `MAX` metadata reactively based on the selected item. This demonstrates how validation rules can have conditional values that change when other fields update. + +### Using metadata in custom controls + +Custom controls can read metadata to configure their HTML attributes and behavior: + +```angular-ts +import {Component, input, computed, model} from '@angular/core'; +import {FormValueControl, Field, PLACEHOLDER} from '@angular/forms/signals'; + +@Component({ + selector: 'custom-input', + template: ` + + `, +}) +export class CustomInput implements FormValueControl { + // Bind to the form field. + formField = input.required>(); + + // Compute the current field state. + state = computed(() => this.formField()()); + + // Required property of the FormValueControl interface. + value = model(0); + + placeholderText = computed(() => this.state().metadata(PLACEHOLDER)() ?? ''); +} +``` + +This pattern allows custom controls to automatically configure themselves based on the validation rules and metadata defined in the schema. + +TIP: For more information on creating custom controls, see the [Custom Controls guide](guide/forms/signals/custom-controls). + +## Combining rules + +You can apply multiple rules to the same field, and you can use conditional logic to apply entire groups of rules based on form state. + +### Multiple rules on one field + +Apply multiple rules to configure all aspects of a field's behavior: + +```angular-ts +import {Component, signal} from '@angular/core'; +import { + form, + FormField, + disabled, + hidden, + debounce, + metadata, + PLACEHOLDER, +} from '@angular/forms/signals'; + +@Component({ + selector: 'app-promo', + imports: [formField], + template: ` + @if (!promoForm.promoCode().hidden()) { + + } + `, +}) +export class Promo { + promoModel = signal({ + hasAccount: false, + subscriptionType: 'free' as 'free' | 'premium', + promoCode: '', + }); + + promoForm = form(this.promoModel, (schemaPath) => { + disabled(schemaPath.promoCode, ({valueOf}) => + !valueOf(schemaPath.hasAccount) ? 'You must have an account' : false, + ); + hidden(schemaPath.promoCode, ({valueOf}) => valueOf(schemaPath.subscriptionType) === 'free'); + debounce(schemaPath.promoCode, 300); + metadata(schemaPath.promoCode, PLACEHOLDER, () => 'Enter promo code'); + }); +} +``` + +These rules work together: + +- Hidden takes precedence - if the field is hidden, disabled state doesn't matter +- Disabled prevents editing regardless of readonly state +- Debouncing affects model updates regardless of other state +- Metadata is independent and always available + +### Conditional logic with applyWhen + +Use `applyWhen()` to conditionally apply entire groups of rules: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {form, FormField, applyWhen, required, pattern} from '@angular/forms/signals'; + +@Component({ + selector: 'app-address', + imports: [formField], + template: ` + + + + `, +}) +export class Address { + addressModel = signal({ + country: 'US', + zipCode: '', + }); + + addressForm = form(this.addressModel, (schemaPath) => { + applyWhen( + schemaPath, + ({valueOf}) => valueOf(schemaPath.country) === 'US', + (schemaPath) => { + // Only applied when country is US + required(schemaPath.zipCode); + pattern(schemaPath.zipCode, /^\d{5}(-\d{4})?$/); + }, + ); + }); +} +``` + +The `applyWhen()` function receives: + +1. A path to apply logic to (often the root form path) +2. A reactive logic function that returns `true` (apply) or `false` (don't apply) +3. A schema function that defines the conditional rules + +The conditional rules only run when the condition is true. This is useful for complex forms where validation rules or behavior changes based on user choices. + +### Reusable schema functions + +Extract common rule configurations into reusable functions: + +```angular-ts +import {SchemaPath, debounce, metadata, maxLength, PLACEHOLDER} from '@angular/forms/signals'; + +function emailFieldConfig(path: SchemaPath) { + debounce(path, 300); + metadata(path, PLACEHOLDER, () => 'user@example.com'); + maxLength(path, 255); +} + +// Use in multiple forms +const contactForm = form(contactModel, (schemaPath) => { + emailFieldConfig(schemaPath.email); + emailFieldConfig(schemaPath.alternateEmail); +}); + +const registrationForm = form(registrationModel, (schemaPath) => { + emailFieldConfig(schemaPath.email); +}); +``` + +This pattern is useful when you have standard field configurations that you use across multiple forms in your application. + +## Next steps + +To learn more about Signal Forms, check out these related guides: + +- [Field State Management](guide/forms/signals/field-state-management) - Learn how to use the state signals created by these functions in your templates and component logic +- [Validation](guide/forms/signals/validation) - Learn about validation rules and error handling +- [Custom Controls](guide/forms/signals/custom-controls) - Learn how custom controls can read metadata and state to configure themselves automatically diff --git a/adev-ja/src/content/guide/forms/signals/migration.md b/adev-ja/src/content/guide/forms/signals/migration.md index ab2d4d82c..a2041eb07 100644 --- a/adev-ja/src/content/guide/forms/signals/migration.md +++ b/adev-ja/src/content/guide/forms/signals/migration.md @@ -1,7 +1,7 @@ # Migrating existing forms to Signal Forms This guide provides strategies for migrating existing codebases to Signal Forms, focusing on interoperability with -legacy Reactive Forms. +existing Reactive Forms. ## Top-down migration using `compatForm` @@ -10,7 +10,7 @@ controls that involve: - Complex asynchronous logic. - Intricate RxJS operators that are not yet ported. -- Integration with legacy third-party libraries. +- Integration with existing third-party libraries. ### Integrating a `FormControl` into a signal form @@ -24,7 +24,7 @@ import {signal} from '@angular/core'; import {FormControl, Validators} from '@angular/forms'; import {compatForm} from '@angular/forms/signals/compat'; -// 1. Existing legacy control with a specialized validator +// 1. Existing control with a specialized validator const passwordControl = new FormControl('', { validators: [Validators.required, enterprisePasswordValidator()], nonNullable: true, @@ -33,7 +33,7 @@ const passwordControl = new FormControl('', { // 2. Wrap it inside your form state signal const user = signal({ email: '', - password: passwordControl, // Nest the legacy control directly + password: passwordControl, // Nest the existing control directly }); // 3. Create the form @@ -45,24 +45,24 @@ console.log(f.password().value()); // Current value of passwordControl // Reactive state is proxied automatically const isPasswordValid = f.password().valid(); -const passwordErrors = f.password().errors(); // Returns CompatValidationError if the legacy validator fails +const passwordErrors = f.password().errors(); // Returns CompatValidationError if the existing validator fails ``` In the template, use standard reactive syntax by binding the underlying control: ```angular-html -
+
@if (f.password().touched() && f.password().invalid()) { @@ -83,14 +83,15 @@ In the template, use standard reactive syntax by binding the underlying control: ### Integrating a `FormGroup` into a signal form -You can also wrap an entire `FormGroup`. This is common when a reusable sub-section of a form—such as an **Address Block**—is still managed by legacy Reactive Forms. +You can also wrap an entire `FormGroup`. This is common when a reusable sub-section of a form—such as an +**Address Block**—is still managed by existing Reactive Forms. ```typescript import {signal} from '@angular/core'; import {FormGroup, FormControl, Validators} from '@angular/forms'; import {compatForm} from '@angular/forms/signals/compat'; -// 1. A legacy address group with its own validation logic +// 1. An existing address group with its own validation logic const addressGroup = new FormGroup({ street: new FormControl('123 Angular Way', Validators.required), city: new FormControl('Mountain View', Validators.required), @@ -109,16 +110,16 @@ const f = compatForm(checkoutModel, (p) => { ``` The `shippingAddress` field acts as a branch in your Signal Form tree. You can bind these nested controls in your -template by accessing the underlying legacy controls via `.control()`: +template by accessing the underlying existing controls via `.control()`: ```angular-html - +

Shipping Details

@if (f.customerName().touched() && f.customerName().invalid()) { @@ -135,7 +136,7 @@ template by accessing the underlying legacy controls via `.control()`:
@if (street.touched && street.invalid) {
@@ -148,7 +149,7 @@ template by accessing the underlying legacy controls via `.control()`:
@if (city.touched && city.invalid) {
@@ -161,7 +162,7 @@ template by accessing the underlying legacy controls via `.control()`:
@if (zip.touched && zip.invalid) {
@@ -187,7 +188,7 @@ const passwordControl = new FormControl('password' /** ... */); const user = signal({ email: '', - password: passwordControl, // Nest the legacy control directly + password: passwordControl, // Nest the existing control directly }); const form = compatForm(user); diff --git a/adev-ja/src/content/guide/i18n/format-data-locale.md b/adev-ja/src/content/guide/i18n/format-data-locale.md index 8f90ef869..3565cf2ef 100644 --- a/adev-ja/src/content/guide/i18n/format-data-locale.md +++ b/adev-ja/src/content/guide/i18n/format-data-locale.md @@ -25,7 +25,7 @@ Add the `locale` parameter to the pipe to override the current value of `LOCALE_ To force the currency to use American English \(`en-US`\), use the following format for the `CurrencyPipe` ```angular-html -{{ amount | currency : 'en-US' }} +{{ amount | currency: 'en-US' }} ``` HELPFUL: The locale specified for the `CurrencyPipe` overrides the global `LOCALE_ID` token of your application. diff --git a/adev-ja/src/content/guide/ngmodules/overview.md b/adev-ja/src/content/guide/ngmodules/overview.md index 76739afce..a66b96124 100644 --- a/adev-ja/src/content/guide/ngmodules/overview.md +++ b/adev-ja/src/content/guide/ngmodules/overview.md @@ -1,6 +1,6 @@ # NgModules -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`. +IMPORTANT: The Angular team recommends using [standalone components](guide/components) 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. @@ -173,7 +173,7 @@ IMPORTANT: The Angular team recommends using [bootstrapApplication](api/platform The `@NgModule` decorator accepts an optional `bootstrap` array that may contain one or more components. -You can use the [`bootstrapModule`](/api/core/PlatformRef#bootstrapModule) method from either [`platformBrowser`](api/platform-browser/platformBrowser) or [`platformServer`](api/platform-server/platformServer) to start an Angular application. When run, this function locates any elements on the page with a CSS selector that matches the listed componet(s) and renders those components on the page. +You can use the [`bootstrapModule`](/api/core/PlatformRef#bootstrapModule) method from either [`platformBrowser`](api/platform-browser/platformBrowser) or [`platformServer`](api/platform-server/platformServer) to start an Angular application. When run, this function locates any elements on the page with a CSS selector that matches the listed component(s) and renders those components on the page. ```typescript import {platformBrowser} from '@angular/platform-browser'; diff --git a/adev-ja/src/content/guide/signals/effect.md b/adev-ja/src/content/guide/signals/effect.md index 211f7d763..3d3e66205 100644 --- a/adev-ja/src/content/guide/signals/effect.md +++ b/adev-ja/src/content/guide/signals/effect.md @@ -35,7 +35,7 @@ By default, you can only create an `effect()` within an [injection context](guid ```ts @Component({ - /* ... */ + /*...*/ }) export class EffectiveCounter { readonly count = signal(0); @@ -52,15 +52,20 @@ export class EffectiveCounter { To create an effect outside the constructor, you can pass an `Injector` to `effect` via its options: ```ts -@Component({...}) -export class EffectiveCounterComponent { +@Component({ + /*...*/ +}) +export class EffectiveCounter { readonly count = signal(0); private injector = inject(Injector); initializeLogging(): void { - effect(() => { - console.log(`The count is: ${this.count()}`); - }, {injector: this.injector}); + effect( + () => { + console.log(`The count is: ${this.count()}`); + }, + {injector: this.injector}, + ); } } ``` @@ -77,7 +82,7 @@ The execution of both kinds of `effect` are tied to the change detection process - "View effects" are executed _before_ their corresponding component is checked by the change detection process. - "Root effects" are executed prior to all components being checked by the change detection process. -In both cases, if at least one of the effect dependencies changed during the effect execution, the effect will re-run before moving ahead on the change detection process, +In both cases, if at least one of the effect dependencies changed during the effect execution, the effect will re-run before moving ahead on the change detection process. ### Destroying effects @@ -117,7 +122,7 @@ For these situations, you can use `afterRenderEffect`. It functions like `effect ```ts @Component({ - /* ... */ + /*...*/ }) export class MyFancyChart { chartData = input.required(); @@ -128,7 +133,7 @@ export class MyFancyChart { // Run a single time to create the chart instance afterNextRender({ write: () => { - this.chart = initializeChart(this.canvas().nativeElement(), this.charData()); + this.chart = initializeChart(this.canvas().nativeElement(), this.chartData()); }, }); @@ -146,7 +151,7 @@ TIP: You often don't need `afterRenderEffect` to check for DOM changes. APIs lik ### Render phases -Accessing the DOM and mutating it can impact the performance of your application, for example by triggering to many unecesary [reflows](https://developer.mozilla.org/en-US/docs/Glossary/Reflow). +Accessing the DOM and mutating it can impact the performance of your application, for example by triggering too many unnecessary [reflows](https://developer.mozilla.org/en-US/docs/Glossary/Reflow). To optimize those operations, `afterRenderEffect` offers four phases to group the callbacks and execute them in an optimized order. diff --git a/adev-ja/src/content/guide/testing/zone-js-testing-utilities.md b/adev-ja/src/content/guide/testing/zone-js-testing-utilities.md index d22cae86e..ce459dc87 100644 --- a/adev-ja/src/content/guide/testing/zone-js-testing-utilities.md +++ b/adev-ja/src/content/guide/testing/zone-js-testing-utilities.md @@ -6,10 +6,10 @@ For general Angular testing utilities, including `TestBed` and `ComponentFixture Here's a summary of Zone.js-specific functions: -| Function | Details | -| :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `waitForAsync` | Tracks async tasks and completes the tests only once there are no longer any micro or macrotasks remaining in the test zone. See [waitForAsync](guide/testing/components-scenarios#waitForAsync). | -| `fakeAsync` | Runs the body of a test \(`it`\) within a special _fakeAsync test zone_, enabling a linear control flow coding style. See [fakeAsync](guide/testing/components-scenarios#fake-async). | -| `tick` | Simulates the passage of time and the completion of pending asynchronous activities by flushing both _timer_ and _micro-task_ queues within the _fakeAsync test zone_. The curious, dedicated reader might enjoy this lengthy blog post, ["_Tasks, microtasks, queues and schedules_"](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules). Accepts an optional argument that moves the virtual clock forward by the specified number of milliseconds, clearing asynchronous activities scheduled within that timeframe. See [tick](guide/testing/components-scenarios#tick). | -| `discardPeriodicTasks` | Discards any periodic tasks (e.g. `setInterval`) that were created inside the `fakeAsync` Zone. | -| `flushMicrotasks` | When a `fakeAsync()` test ends with pending _micro-tasks_ such as unresolved promises, the test fails with a clear error message.
In general, a test should wait for micro-tasks to finish. When pending microtasks are expected, call `flushMicrotasks` to flush the _micro-task_ queue and avoid the error. | +| Function | Details | +| :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `waitForAsync` | Tracks async tasks and completes the tests only once there are no longer any micro or macrotasks remaining in the test zone. | +| `fakeAsync` | Runs the body of a test \(`it`\) within a special _fakeAsync test zone_, enabling a linear control flow coding style. | +| `tick` | Simulates the passage of time and the completion of pending asynchronous activities by flushing both _timer_ and _micro-task_ queues within the _fakeAsync test zone_. The curious, dedicated reader might enjoy this lengthy blog post, ["_Tasks, microtasks, queues and schedules_"](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules). Accepts an optional argument that moves the virtual clock forward by the specified number of milliseconds, clearing asynchronous activities scheduled within that timeframe. | +| `discardPeriodicTasks` | Discards any periodic tasks (e.g. `setInterval`) that were created inside the `fakeAsync` Zone. | +| `flushMicrotasks` | When a `fakeAsync()` test ends with pending _micro-tasks_ such as unresolved promises, the test fails with a clear error message.
In general, a test should wait for micro-tasks to finish. When pending microtasks are expected, call `flushMicrotasks` to flush the _micro-task_ queue and avoid the error. | diff --git a/adev-ja/src/content/reference/configs/angular-compiler-options.md b/adev-ja/src/content/reference/configs/angular-compiler-options.md index 539383d5c..4fccf7bef 100644 --- a/adev-ja/src/content/reference/configs/angular-compiler-options.md +++ b/adev-ja/src/content/reference/configs/angular-compiler-options.md @@ -132,7 +132,7 @@ The `module` field of the library's `package.json` would be `"index.js"` and the ### `fullTemplateTypeCheck` -When `true`, the recommended value, enables the [binding expression validation](tools/cli/aot-compiler#binding-expression-validation) phase of the template compiler. This phase uses TypeScript to verify binding expressions. +When `true`, the recommended value, enables the binding expression validation phase of the template compiler. This phase uses TypeScript to verify binding expressions. For more information, see [Template type checking](tools/cli/template-typecheck). Default is `false`, but when you use the Angular CLI command `ng new --strict`, it is set to `true` in the new project's configuration. @@ -151,7 +151,7 @@ Do this when using factory summaries. When `false`, the default, removes blank text nodes from compiled templates, which results in smaller emitted template factory modules. Set to `true` to preserve blank text nodes. -HELPFUL: When using hydration, it is recommended that you use `preserveWhitespaces: false`, which is the default value. If you choose to enable preserving whitespaces by adding `preserveWhitespaces: true` to your tsconfig, it is possible you may encounter issues with hydration. This is not yet a fully supported configuration. Ensure this is also consistently set between the server and client tsconfig files. See the [hydration guide](guide/hydration#preserve-whitespaces) for more details. +HELPFUL: When using hydration, it is recommended that you use `preserveWhitespaces: false`, which is the default value. If you choose to enable preserving whitespaces by adding `preserveWhitespaces: true` to your tsconfig, it is possible you may encounter issues with hydration. This is not yet a fully supported configuration. Ensure this is also consistently set between the server and client tsconfig files. See the [hydration guide](guide/hydration#preserve-whitespaces-configuration) for more details. ### `skipMetadataEmit` diff --git a/adev-ja/src/content/reference/configs/workspace-config.md b/adev-ja/src/content/reference/configs/workspace-config.md index 1757ff391..151c4b8b7 100644 --- a/adev-ja/src/content/reference/configs/workspace-config.md +++ b/adev-ja/src/content/reference/configs/workspace-config.md @@ -208,16 +208,16 @@ In this example, if both `staging` and `french` configurations set the output pa The configurable options for a default or targeted build generally correspond to the options available for the [`ng build`](cli/build), and [`ng test`](cli/test) commands. For details of those options and their possible values, see the [Angular CLI Reference](cli). -| Options properties | Details | -| :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `assets` | An object containing paths to static assets to serve with the application. The default paths point to the project's `public` directory. See more in the [Assets configuration](#assets-configuration) section. | -| `styles` | An array of CSS files to add to the global context of the project. Angular CLI supports CSS imports and all major CSS preprocessors. See more in the [Styles and scripts configuration](#styles-and-scripts-configuration) section. | -| `stylePreprocessorOptions` | An object containing option-value pairs to pass to style preprocessors. See more in the [Styles and scripts configuration](#styles-and-scripts-configuration) section. | -| `scripts` | An object containing JavaScript files to add to the application. The scripts are loaded exactly as if you had added them in a `