From 7b85bcfb3076c37993b83139dfb7ec46f205348d Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sat, 8 Aug 2026 22:05:18 +0900 Subject: [PATCH 01/59] chore: update origin to 4d985a179e --- origin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/origin b/origin index fa546f382..4d985a179 160000 --- a/origin +++ b/origin @@ -1 +1 @@ -Subproject commit fa546f382de10af46d0508733c6630ffe4bef328 +Subproject commit 4d985a179e66428d46e60b4622435f88c39e1d2b From 63bed00e96934f37bc9bb5b5afb8b440fe38ae81 Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sat, 8 Aug 2026 22:05:35 +0900 Subject: [PATCH 02/59] fix: migrate untranslated files --- .../profiling-with-chrome-devtools.md | 12 + .../ecosystem/rxjs-interop/output-interop.md | 4 +- .../di/debugging-and-troubleshooting-di.md | 2 +- .../guide/di/defining-dependency-providers.md | 2 +- adev-ja/src/content/guide/drag-drop.md | 6 +- .../guide/forms/signals/async-operations.md | 17 + .../signals/designing-your-form-model.md | 4 +- .../forms/signals/dynamic-forms-with-json.md | 481 ++++++++++++++++++ .../guide/forms/signals/field-metadata.md | 40 +- .../content/guide/forms/signals/form-logic.md | 20 +- .../content/guide/forms/signals/migration.md | 65 +++ .../content/guide/forms/signals/testing.md | 214 ++++++++ .../src/content/guide/http/http-resource.md | 12 +- .../content/guide/i18n/format-data-locale.md | 2 +- adev-ja/src/content/guide/i18n/prepare.md | 4 +- .../content/guide/i18n/translation-files.md | 4 +- .../src/content/guide/signals/debounced.md | 2 +- adev-ja/src/content/guide/signals/effect.md | 14 +- adev-ja/src/content/guide/testing/services.md | 6 +- .../configs/angular-compiler-options.md | 3 +- .../reference/configs/workspace-config.md | 3 +- .../src/content/reference/errors/NG0205.md | 12 +- .../src/content/reference/errors/NG02825.md | 37 ++ .../src/content/reference/errors/NG0318.md | 28 + .../src/content/reference/errors/NG05101.md | 26 + .../src/content/reference/errors/NG05102.md | 44 ++ .../src/content/reference/errors/NG05201.md | 53 ++ .../src/content/reference/errors/NG05703.md | 25 + adev-ja/src/content/reference/press-kit.md | 4 + adev-ja/src/content/tools/cli/aot-compiler.md | 2 +- .../tools/cli/build-system-migration.md | 4 +- adev-ja/src/content/tools/cli/cli-builder.md | 4 +- .../content/tools/cli/schematics-authoring.md | 6 +- .../tools/cli/schematics-for-libraries.md | 13 +- adev-ja/src/content/tools/cli/schematics.md | 2 +- .../tools/libraries/angular-package-format.md | 2 +- .../tools/libraries/creating-libraries.md | 4 +- 37 files changed, 1096 insertions(+), 87 deletions(-) create mode 100644 adev-ja/src/content/guide/forms/signals/dynamic-forms-with-json.md create mode 100644 adev-ja/src/content/guide/forms/signals/testing.md create mode 100644 adev-ja/src/content/reference/errors/NG02825.md create mode 100644 adev-ja/src/content/reference/errors/NG0318.md create mode 100644 adev-ja/src/content/reference/errors/NG05101.md create mode 100644 adev-ja/src/content/reference/errors/NG05102.md create mode 100644 adev-ja/src/content/reference/errors/NG05201.md create mode 100644 adev-ja/src/content/reference/errors/NG05703.md 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 d992e529a..fa21d3682 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 @@ -51,6 +51,18 @@ Use the **Record** button in the Chrome DevTools performance panel: See the [Chrome DevTools documentation](https://developer.chrome.com/docs/devtools/performance#record) for more details on recording profiles. +## Open a component in Angular DevTools + +After recording a profile, select a component event in the **Angular** track. +The **Summary** tab can include a **Component** link that uses the `angular-devtools://component/...` URL scheme. + +Chrome DevTools Performance panel showing an Angular custom track with a selected _MainComponent event. The Summary tab displays a Component link that uses the angular-devtools://component URL scheme. + +Click the link to open Angular DevTools and select the matching component in the **Components** tab. +This helps you move from a browser-level profile to the component state and metadata for a selected event. + +NOTE: Opening component links requires Angular DevTools for Chrome and Chrome's experimental `chrome://flags/#enable-devtools-deep-link-via-extensibility-api` flag. + ## Interpreting a recorded profile You can use the "Angular" custom track to quickly identify and diagnose performance issues. The following sections describe some common profiling scenarios. diff --git a/adev-ja/src/content/ecosystem/rxjs-interop/output-interop.md b/adev-ja/src/content/ecosystem/rxjs-interop/output-interop.md index ae5abe871..3a5fef2cd 100644 --- a/adev-ja/src/content/ecosystem/rxjs-interop/output-interop.md +++ b/adev-ja/src/content/ecosystem/rxjs-interop/output-interop.md @@ -12,9 +12,7 @@ The `outputFromObservable` lets you create a component or directive output that import {Directive} from '@angular/core'; import {outputFromObservable} from '@angular/core/rxjs-interop'; -@Directive({ - /*...*/ -}) +@Directive(/* ... */) class Draggable { pointerMoves$: Observable = listenToPointerMoves(); 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 index e41812c9c..6d629f282 100644 --- a/adev-ja/src/content/guide/di/debugging-and-troubleshooting-di.md +++ b/adev-ja/src/content/guide/di/debugging-and-troubleshooting-di.md @@ -440,7 +440,7 @@ TIP: Always export tokens from a shared file and import them everywhere they're 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'} +```angular-ts {avoid, header: "Can't inject interface"} interface UserConfig { name: string; email: string; 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 0de69a6f0..580ba7b2a 100644 --- a/adev-ja/src/content/guide/di/defining-dependency-providers.md +++ b/adev-ja/src/content/guide/di/defining-dependency-providers.md @@ -2,7 +2,7 @@ Angular provides two ways to make services available for injection: -1. **Automatic provision** - Using `providedIn` in the `@Injectable` decorator, the [`@Service`](guide/di/creating-and-using-services#using-the-service-decorator) decorator, or by providing a factory in the `InjectionToken` configuration +1. **Automatic provision** - Using `providedIn` in the `@Injectable` decorator, the [`@Service`](guide/di/creating-and-using-services#using-the-service-vs-injectable-decorator) decorator, or by providing a factory in the `InjectionToken` configuration 2. **Manual provision** - Using the `providers` array in components, directives, routes, or application config In the [previous guide](/guide/di/creating-and-using-services), you learned how to create services using `providedIn: 'root'`, which handles most common use cases. This guide explores additional patterns for both automatic and manual provider configuration. diff --git a/adev-ja/src/content/guide/drag-drop.md b/adev-ja/src/content/guide/drag-drop.md index 91769cd4b..563cdcd96 100644 --- a/adev-ja/src/content/guide/drag-drop.md +++ b/adev-ja/src/content/guide/drag-drop.md @@ -2,7 +2,7 @@ ## Overview -This page describes the drag and drop directives which lets you quickly create drag and drop interfaces with the following: +This page describes the drag and drop directives which let you quickly create drag and drop interfaces with the following: - Free dragging - Create a list of reorderable draggable elements @@ -227,7 +227,7 @@ Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update bo ### Restrict movement along an axis -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. +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. @@ -281,7 +281,7 @@ Alternatively, you can modify the `CDK_DRAG_CONFIG` injection token to update `l By default, the `cdkDropList` sorts the draggable elements by moving them around using a CSS transform. This allows for the sorting to be animated which provides a better user experience. However this also comes with the drawback that the drop list works only in one direction: vertically or horizontally. -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 . +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. diff --git a/adev-ja/src/content/guide/forms/signals/async-operations.md b/adev-ja/src/content/guide/forms/signals/async-operations.md index a0542c948..95ce8f7a9 100644 --- a/adev-ja/src/content/guide/forms/signals/async-operations.md +++ b/adev-ja/src/content/guide/forms/signals/async-operations.md @@ -180,6 +180,23 @@ onSuccess: (response: { usernameTaken: boolean; profanity: boolean }) => { } // prettier-ignore ``` +The type for `onSuccess` can be specified either directly in the parameter, or with the `parse` property of `validateHttp`'s `options` + +```ts +onSuccess: (response: { usernameTaken: boolean; profanity: boolean }) => { + // ... +} // prettier-ignore + +// or + +options: { + parse: (response) => response as {usernameTaken: boolean; profanity: boolean}; +} +onSuccess: (response) => { + // ... +} // prettier-ignore +``` + The `onError` function handles request failures like network errors or HTTP errors: ```ts 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 f851af4d4..e4389576f 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 @@ -380,7 +380,7 @@ class MyForm { handleSubmit() { submit(this.myForm, async () => { - await this.myDataService.update(formModelToDomainModel(this.myForm.value())); + await this.myDataService.update(formModelToDomainModel(this.myForm().value())); }); }; } @@ -402,7 +402,7 @@ class MyForm { effect(() => { // When the form model changes to a valid value, update the domain model. if (this.myForm().valid()) { - this.domainModel.set(formModelToDomainModel(this.myForm.value())); + this.domainModel.set(formModelToDomainModel(this.myForm().value())); } }); }; diff --git a/adev-ja/src/content/guide/forms/signals/dynamic-forms-with-json.md b/adev-ja/src/content/guide/forms/signals/dynamic-forms-with-json.md new file mode 100644 index 000000000..5076120b6 --- /dev/null +++ b/adev-ja/src/content/guide/forms/signals/dynamic-forms-with-json.md @@ -0,0 +1,481 @@ +# Dynamic Forms with JSON + +Some forms can't define their structure at compile time. Server-driven forms, admin panels, multi-tenant applications, and CMS-managed content all need to render fields from configuration delivered at runtime, typically as JSON from a backend, an admin tool, or per-tenant settings. + +This guide shows how to build forms whose model, schema, validation, and rendering are all derived from a single runtime configuration. + +## When to use JSON-driven forms + +This pattern is a good choice when: + +- A backend defines which fields appear based on user role, feature flags, or business rules +- Non-developers configure form structure through an admin panel or CMS +- Each tenant in a multi-tenant application has its own form structure stored as configuration +- Forms need to evolve without redeploying the frontend + +Use a static form (with fields defined directly in your component) when the structure is known at build time. Static forms get full TypeScript checking on every field, plus straightforward testing and tooling support. + +## Defining a typed field config + +When you want to render fields from runtime configuration, start with a TypeScript type that captures the shape of each field. A discriminated union by `kind` lets each variant declare its own validation options: + +```ts +type FieldConfig = + | {kind: 'text'; name: string; label: string; required?: boolean} + | {kind: 'number'; name: string; label: string; required?: boolean; min?: number; max?: number}; +``` + +Every variant has a name, label, and optional `required` flag. Number fields additionally accept `min` and `max` bounds. Add new variants by adding new `kind` branches. + +A concrete config might look like this: + +```ts +const profileConfig: FieldConfig[] = [ + {kind: 'text', name: 'fullName', label: 'Full Name', required: true}, + {kind: 'number', name: 'age', label: 'Age', required: true, min: 18, max: 120}, +]; +``` + +In practice this `FieldConfig[]` typically arrives from your backend, an admin panel, or a CMS. For brevity, the examples below use an in-component literal. + +## Building the model from config + +The form's model needs one entry per field with a default value matching the field's kind. A small helper handles this: + +```ts +function buildModel(configs: FieldConfig[]): Record { + const initial: Record = {}; + for (const config of configs) { + initial[config.name] = config.kind === 'number' ? null : ''; + } + return initial; +} +``` + +The model uses `Record` because the keys are not known ahead of time. + +In addition, numeric fields initialize to `null` rather than `0` so an empty field reads as empty. With `0`, [`required()`](api/forms/signals/required) would treat the field as already filled, and any [`min()`](api/forms/signals/min) constraint above zero would flag the field invalid before the user enters anything. + +## Building the schema from config + +The schema is also derived from the config. You can loop through each entry and apply the validators that match its kind: + +```ts +import {required, min, max, SchemaFn} from '@angular/forms/signals'; + +function buildSchema(configs: FieldConfig[]): SchemaFn> { + return (path) => { + for (const config of configs) { + const fieldPath = path[config.name]; + + if (config.required) { + required(fieldPath); + } + + if (config.kind === 'number') { + if (config.min !== undefined) min(fieldPath, config.min); + if (config.max !== undefined) max(fieldPath, config.max); + } + } + }; +} +``` + +The discriminated union narrows `config` inside each branch, so `config.min` and `config.max` are typed correctly when `config.kind === 'number'`. + +## Expressing conditional rules in config + +Some validation rules only make sense under certain conditions. For example, US state codes need validating only when the country is the US. Express these dependencies in the config by adding a `when` discriminator that names another field and the value it must equal: + +```ts +type WhenCondition = {field: string; equals: string | number}; + +type FieldConfig = + | {kind: 'text'; name: string; label: string; required?: boolean; when?: WhenCondition} + | { + kind: 'number'; + name: string; + label: string; + required?: boolean; + min?: number; + max?: number; + when?: WhenCondition; + }; +``` + +Update `buildSchema()` to translate `when` into an [`applyWhen()`](api/forms/signals/applyWhen) call. Shared rule application logic moves into a small closure so the conditional and unconditional branches both call the same function: + +```ts +import {applyWhen, required, min, max, SchemaFn} from '@angular/forms/signals'; + +function buildSchema(configs: FieldConfig[]): SchemaFn> { + return (rootPath) => { + for (const config of configs) { + const applyRules = (path: typeof rootPath) => { + const fieldPath = path[config.name]; + if (config.required) required(fieldPath); + if (config.kind === 'number') { + if (config.min !== undefined) min(fieldPath, config.min); + if (config.max !== undefined) max(fieldPath, config.max); + } + }; + + if (config.when) { + const {field, equals} = config.when; + applyWhen(rootPath, ({valueOf}) => valueOf(rootPath[field]) === equals, applyRules); + } else { + applyRules(rootPath); + } + } + }; +} +``` + +When `applyWhen()`'s condition is true, the rules inside activate. When the condition becomes false, the rules deactivate and the field's validation state clears. Because the condition function reads through `valueOf(rootPath[field])`, the form re-evaluates the gate every time the referenced field changes. + +A config that uses `when` looks like this: + +```ts +const addressConfig: FieldConfig[] = [ + {kind: 'text', name: 'country', label: 'Country', required: true}, + { + kind: 'text', + name: 'stateCode', + label: 'State', + required: true, + when: {field: 'country', equals: 'US'}, + }, +]; +``` + +The `stateCode` field only requires a value when `country` is `'US'`. Users entering any other country can leave `stateCode` blank without blocking submission. + +For more complex conditions (multiple fields, ranges, or non-equality checks), extend `WhenCondition` with additional discriminators (such as `in: string[]` or `notEquals: string | number`) and translate each variant inside `buildSchema()`. The principle is the same: the config carries the data, `buildSchema()` translates it to `applyWhen()` calls. + +To gate visibility instead of validation, follow the same pattern using [`hidden()`](api/forms/signals/hidden) on the field path. See [Configuring `hidden()` state on fields](guide/forms/signals/form-logic#configuring-hidden-state-on-fields) for details. + +## Expressing repeating fields in config + +Some configurations need fields that grow and shrink at runtime, like a list of phone numbers, tags, or invoice line items. Add an `array` kind to the config and translate it to [`applyEach()`](api/forms/signals/applyEach) so per-item rules apply uniformly as items come and go. + +Extend `FieldConfig` with an `array` variant. This example uses arrays of strings; the same approach scales to arrays of objects by replacing the item shape with a record: + +```ts +type FieldConfig = + | {kind: 'text'; name: string; label: string; required?: boolean; when?: WhenCondition} + | { + kind: 'number'; + name: string; + label: string; + required?: boolean; + min?: number; + max?: number; + when?: WhenCondition; + } + | {kind: 'array'; name: string; label: string; itemRequired?: boolean; when?: WhenCondition}; +``` + +Update `buildModel()` to initialize array fields with an empty array. The model widens to include `string[]`: + +```ts +function buildModel(configs: FieldConfig[]): Record { + const initial: Record = {}; + for (const config of configs) { + if (config.kind === 'number') initial[config.name] = null; + else if (config.kind === 'array') initial[config.name] = []; + else initial[config.name] = ''; + } + return initial; +} +``` + +Update `buildSchema()` to apply per-item rules with `applyEach()`. The path coming from a `Record` model is too broad for `applyEach()` (and for `min()` / `max()`) to type-check directly, so cast `fieldPath` to the appropriate shape inside each `kind` branch: + +```ts +import { + applyEach, + applyWhen, + required, + min, + max, + SchemaFn, + SchemaPath, +} from '@angular/forms/signals'; + +function buildSchema( + configs: FieldConfig[], +): SchemaFn> { + return (rootPath) => { + for (const config of configs) { + const applyRules = (path: typeof rootPath) => { + const fieldPath = path[config.name]; + + if (config.kind === 'array') { + const arrayPath = fieldPath as unknown as SchemaPath; + if (config.itemRequired) { + applyEach(arrayPath, (item) => required(item)); + } + return; + } + + if (config.required) required(fieldPath); + + if (config.kind === 'number') { + const numberPath = fieldPath as unknown as SchemaPath; + if (config.min !== undefined) min(numberPath, config.min); + if (config.max !== undefined) max(numberPath, config.max); + } + }; + + if (config.when) { + const {field, equals} = config.when; + applyWhen(rootPath, ({valueOf}) => valueOf(rootPath[field]) === equals, applyRules); + } else { + applyRules(rootPath); + } + } + }; +} +``` + +The casts inside each branch are deliberate escape hatches: you're trading the compiler's structural guarantee for a runtime invariant the surrounding `kind` check enforces. Each cast is scoped to a `kind` block, so the assumption stays local and straightforward to audit. + +A config that uses the `array` kind looks like this: + +```ts +const contactConfig: FieldConfig[] = [ + {kind: 'text', name: 'fullName', label: 'Full name', required: true}, + {kind: 'array', name: 'phoneNumbers', label: 'Phone numbers', itemRequired: true}, +]; +``` + +To render an array field, iterate it with `@for` and let users add or remove items by updating the model signal. Add a typed accessor that returns a [`FieldTree`](api/forms/signals/FieldTree) so the iteration sees the array structure, and define methods to grow and shrink the model: + +```ts +import {FieldTree} from '@angular/forms/signals'; + +// inside the component class +asArrayField(name: string): FieldTree { + return this.dynamicForm[name] as unknown as FieldTree; +} + +addItem(name: string) { + this.model.update(current => ({ + ...current, + [name]: [...(current[name] as string[]), ''], + })); +} + +removeItem(name: string, index: number) { + this.model.update(current => ({ + ...current, + [name]: (current[name] as string[]).filter((_, i) => i !== index), + })); +} +``` + +`FieldTree` is iterable, so `@for` can walk it; each item is a `FieldTree` that satisfies `[formField]` directly. Leaf fields can use `Field` accessors instead, since `Field` is the callable signature without iteration. + +In the template, render an array case like this: + +```angular-html +@case ('array') { +
+ {{ config.label }} + @for (item of asArrayField(config.name); track item) { + + + } + +
+} +``` + +The `addItem()` method extends the model; the form re-derives the array's fields automatically. New items start with fresh validation state. `removeItem()` filters the model; the dropped item's field state goes with it. + +### Tracking item identity + +Signal Forms tracks each item in an array of objects by its identity. When you store a reference to a field at a specific position, that reference follows the underlying data, not the position. Reading state through the held reference returns the data even if it has moved: + +```ts +const contactModel = signal([ + {name: 'Alice', phone: '555-0001'}, + {name: 'Bob', phone: '555-0002'}, +]); + +const contactForm = form(contactModel); + +// Hold a reference to the field that's currently at index 0 (Alice). +const aliceField = contactForm[0]; + +// Swap the array items so Bob is at index 0, Alice at index 1. +contactModel.update(([alice, bob]) => [bob, alice]); + +// The held reference still points to Alice's field, even after the swap. +console.log(aliceField().value().phone); // '555-0001' (Alice's number) +console.log(contactForm[0]().value().phone); // '555-0002' (Bob, now at index 0) +``` + +This identity tracking prevents bugs when sorting, reordering, or filtering, as long as the referenced item remains in the array. Stored field references remain valid even when array order changes; removing the referenced item itself orphans the held reference. + +For arrays of primitives (the `phoneNumbers` example above), Signal Forms tracks items positionally instead: index 0 always refers to whatever value is currently at position 0. + +Identity here is by JavaScript object reference, not by a logical id like a database key. If you replace the array with freshly-deserialized objects (for example, after a server reload), field state doesn't follow the logical item even when each item's `id` is unchanged. The guarantee covers in-memory operations like sorting, reordering, and filtering, not data refresh. + +## Validating the config + +Configs from external sources need validation before the form is built. Several failure modes can hide in untrusted JSON: + +- Duplicate `name` values overwrite earlier model entries and break the `track config.name` expression in the template. +- A `when` clause that names a non-existent field fails at runtime when the condition first evaluates. +- A `when` clause that compares against an `array` field has no defined equality semantics. +- A `when.equals` value whose type doesn't match the referenced field's kind silently never matches, hiding the conditional behavior as if the rule were never active. + +Catch all four at the boundary: + +```ts +function validateConfigs(configs: FieldConfig[]): FieldConfig[] { + const knownNames = new Set(); + + for (const config of configs) { + if (knownNames.has(config.name)) { + throw new Error(`Duplicate field name in config: "${config.name}"`); + } + knownNames.add(config.name); + } + + for (const config of configs) { + if (!config.when) continue; + if (!knownNames.has(config.when.field)) { + throw new Error( + `Field "${config.name}" references unknown field "${config.when.field}" in its 'when' condition.`, + ); + } + const referenced = configs.find((c) => c.name === config.when!.field)!; + if (referenced.kind === 'array') { + throw new Error( + `Field "${config.name}" cannot use 'when' to compare against array field "${config.when.field}".`, + ); + } + const expected = referenced.kind === 'text' ? 'string' : 'number'; + if (typeof config.when.equals !== expected) { + throw new Error( + `Field "${config.name}" compares ${referenced.kind} field "${config.when.field}" against a ${typeof config.when.equals} value; expected a ${expected}.`, + ); + } + } + + return configs; +} +``` + +The first pass enforces uniqueness; the second pass walks each `when` clause to confirm the referenced field exists, isn't an array, and is being compared against a value of the right type. The function returns the configs unchanged on success, so it composes cleanly with the field initializer that holds the configs in the component. Failures surface at the boundary between your application and the upstream source rather than as opaque form misbehavior later. + +## Rendering the form dynamically + +In the component, use `@for` to iterate the configs and `@switch` on `kind` to pick the right input control: + +```angular-ts +import {Component, signal} from '@angular/core'; +import {Field, FieldTree, form, FormField, FormRoot} from '@angular/forms/signals'; + +@Component({ + selector: 'app-dynamic-form', + imports: [FormField, FormRoot], + template: ` +
+ @for (config of configs; track config.name) { + @switch (config.kind) { + @case ('text') { + + } + @case ('number') { + + } + @case ('array') { +
+ {{ config.label }} + @for (item of asArrayField(config.name); track item; let i = $index) { + + + } + +
+ } + } + } +
+ `, +}) +export class DynamicForm { + configs: FieldConfig[] = validateConfigs([ + {kind: 'text', name: 'fullName', label: 'Full Name', required: true}, + {kind: 'number', name: 'age', label: 'Age', required: true, min: 18, max: 120}, + {kind: 'array', name: 'phoneNumbers', label: 'Phone numbers', itemRequired: true}, + ]); + + model = signal(buildModel(this.configs)); + + dynamicForm = form(this.model, buildSchema(this.configs)); + + asTextField(name: string): Field { + // requires Field. + return this.dynamicForm[name] as unknown as Field; + } + + asNumberField(name: string): Field { + // requires Field. + return this.dynamicForm[name] as unknown as Field; + } + + asArrayField(name: string): FieldTree { + // FieldTree (not Field) so @for can iterate the array. + return this.dynamicForm[name] as unknown as FieldTree; + } + + addItem(name: string) { + this.model.update((current) => ({ + ...current, + [name]: [...(current[name] as string[]), ''], + })); + } + + removeItem(name: string, index: number) { + this.model.update((current) => ({ + ...current, + [name]: (current[name] as string[]).filter((_, i) => i !== index), + })); + } +} +``` + +Template type-checking treats `dynamicForm[name]` as an independent expression, so the `@switch` narrowing on `config.kind` doesn't reach the indexed access. The accessors restate that narrowing as a cast at the binding site, and the matching `kind` branch guarantees the narrowed type is correct at runtime. + +Because the model and schema are both derived from the same `FieldConfig[]` at component construction, they can't drift apart for a given config. The example above assumes the config is available synchronously when the component is created. + +## Next steps + +JSON-driven forms keep their model and schema aligned by deriving both from the same `FieldConfig[]`. Each extension in this guide (conditional rules, repeating fields) widens the type and adds a translation step inside `buildSchema()` while preserving that alignment. The model and schema stay locked together, regardless of where the config comes from or how it grows. + +For related guides cover other aspects of Signal Forms, check out: + + + + + + + + +For detailed API documentation, see: + +- [`form()`](api/forms/signals/form) - Create a form from a model signal +- [`applyWhen()`](api/forms/signals/applyWhen) - Apply a schema conditionally based on reactive state +- [`applyEach()`](api/forms/signals/applyEach) - Apply a schema to each item in an array field +- [`FieldTree`](api/forms/signals/FieldTree) - Navigable tree of fields exposed by `form()` +- [`SchemaFn`](api/forms/signals/SchemaFn) - Type signature for schema functions diff --git a/adev-ja/src/content/guide/forms/signals/field-metadata.md b/adev-ja/src/content/guide/forms/signals/field-metadata.md index d2f5d7092..a35a117a2 100644 --- a/adev-ja/src/content/guide/forms/signals/field-metadata.md +++ b/adev-ja/src/content/guide/forms/signals/field-metadata.md @@ -40,14 +40,16 @@ Calling `required(path.username)` contributes a value to the `REQUIRED` metadata Several built-in constraint validators follow this pattern: -| Validator | Metadata key | Type | `FieldState` getter | -| ------------- | ------------ | --------------------- | ------------------- | -| `required()` | `REQUIRED` | `boolean` | `required` | -| `min()` | `MIN` | `number \| undefined` | `min` | -| `max()` | `MAX` | `number \| undefined` | `max` | -| `minLength()` | `MIN_LENGTH` | `number \| undefined` | `minLength` | -| `maxLength()` | `MAX_LENGTH` | `number \| undefined` | `maxLength` | -| `pattern()` | `PATTERN` | `RegExp[]` | `pattern` | +| Validator | Metadata key | Type | `FieldState` getter | +| ------------- | -------------------------- | --------------------- | ------------------- | +| `required()` | `REQUIRED` | `boolean` | `required` | +| `min()` | `MIN` selects `MIN_NUMBER` | `number \| undefined` | `min` | +| `max()` | `MAX` selects `MAX_NUMBER` | `number \| undefined` | `max` | +| `minDate()` | `MIN` selects `MIN_DATE` | `Date \| undefined` | `min` | +| `maxDate()` | `MAX` selects `MAX_DATE` | `Date \| undefined` | `max` | +| `minLength()` | `MIN_LENGTH` | `number \| undefined` | `minLength` | +| `maxLength()` | `MAX_LENGTH` | `number \| undefined` | `maxLength` | +| `pattern()` | `PATTERN` | `RegExp[]` | `pattern` | Non-constraint validators like `email()` and `validate()` do not contribute to metadata. They run their check and surface a validation error, but they do not publish a reactive value for templates to read. @@ -217,16 +219,20 @@ While `MetadataReducer.min()` and `MetadataReducer.max()` are reducers, you may The built-in constraint keys pick their reducers based on what "strictest" means for the constraint, which is often the opposite of what the key's name suggests: -| Key | Reducer | Reasoning | -| ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `REQUIRED` | `or()` | If any `required()` rule evaluates to `true`, the field is required. | -| `MIN` | `max()` | A minimum-value constraint is strictest when largest. If one rule requires `>= 5` and another `>= 10`, the effective minimum is `10`. | -| `MAX` | `min()` | A maximum-value constraint is strictest when smallest. If one rule caps at `100` and another at `50`, the effective maximum is `50`. | -| `MIN_LENGTH` | `max()` | Same logic as `MIN`: the longest required length wins. | -| `MAX_LENGTH` | `min()` | Same logic as `MAX`: the shortest allowed length wins. | -| `PATTERN` | `list()` | Each `pattern()` call contributes a regex; the value must match all of them. | +| Key | Reducer | Reasoning | +| ------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `REQUIRED` | `or()` | If any `required()` rule evaluates to `true`, the field is required. | +| `MIN_NUMBER` | `max()` | A minimum-number constraint is strictest when largest. If one rule requires `>= 5` and another `>= 10`, the effective minimum is `10`. | +| `MIN_DATE` | `max()` | Same logic as `MIN_NUMBER`: the latest required date wins. | +| `MAX_NUMBER` | `min()` | A maximum-number constraint is strictest when smallest. If one rule caps at `100` and another at `50`, the effective maximum is `50`. | +| `MAX_DATE` | `min()` | Same logic as `MAX_NUMBER`: the earliest allowed date wins. | +| `MIN_LENGTH` | `max()` | Same logic as `MIN_NUMBER`: the longest required length wins. | +| `MAX_LENGTH` | `min()` | Same logic as `MAX_NUMBER`: the shortest allowed length wins. | +| `PATTERN` | `list()` | Each `pattern()` call contributes a regex; the value must match all of them. | -This pairing of "strictest wins" is why calling `min(path.age, 18)` and `min(path.age, 21)` in two composed schemas works correctly. Each call registers its own validator that enforces its specific bound (so a value below either bound fails validation). Separately, each call contributes to the public `MIN` key, and `state.metadata(MIN)!()` reports the aggregate (`21`) so UI and custom controls can read the effective minimum. +`MIN` and `MAX` are selection keys. They point to the concrete key that matches the field's value type, such as `MIN_NUMBER` for `min()` and `MIN_DATE` for `minDate()`. This is why `field().min()` and `field().max()` work for both numeric and date fields. + +This pairing of "strictest wins" is why calling `min(path.age, 18)` and `min(path.age, 21)` in two composed schemas works correctly. Each call registers its own validator that enforces its specific bound (so a value below either bound fails validation). Separately, each call contributes to the `MIN_NUMBER` key, and `state.min!()` reports the aggregate (`21`) so UI and custom controls can read the effective minimum. ### Writing a custom reducer diff --git a/adev-ja/src/content/guide/forms/signals/form-logic.md b/adev-ja/src/content/guide/forms/signals/form-logic.md index 8ff94d751..c5a5a87c8 100644 --- a/adev-ja/src/content/guide/forms/signals/form-logic.md +++ b/adev-ja/src/content/guide/forms/signals/form-logic.md @@ -458,16 +458,16 @@ Don't use debouncing if: Metadata attaches reactive data to a field. Validation rules use this system internally, and you can publish your own keys for application-specific information like help text, configuration, or computed display values. -Signal Forms provides six pre-defined metadata keys that built-in validators populate automatically: - -| Key | Populated by | Read via | -| ------------ | ------------- | --------------------- | -| `REQUIRED` | `required()` | `field().required()` | -| `MIN` | `min()` | `field().min()` | -| `MAX` | `max()` | `field().max()` | -| `MIN_LENGTH` | `minLength()` | `field().minLength()` | -| `MAX_LENGTH` | `maxLength()` | `field().maxLength()` | -| `PATTERN` | `pattern()` | `field().pattern()` | +Signal Forms provides pre-defined metadata keys that built-in validators populate automatically: + +| Key | Populated by | Read via | +| ------------ | -------------------- | --------------------- | +| `REQUIRED` | `required()` | `field().required()` | +| `MIN` | `min()`, `minDate()` | `field().min()` | +| `MAX` | `max()`, `maxDate()` | `field().max()` | +| `MIN_LENGTH` | `minLength()` | `field().minLength()` | +| `MAX_LENGTH` | `maxLength()` | `field().maxLength()` | +| `PATTERN` | `pattern()` | `field().pattern()` | The `[formField]` directive automatically binds five of these (`REQUIRED`, `MIN`, `MAX`, `MIN_LENGTH`, and `MAX_LENGTH`) to the corresponding HTML attribute on a native form control. `PATTERN` is the exception, because Signal Forms supports multiple patterns per field but the HTML `pattern` attribute accepts only a single regular expression. diff --git a/adev-ja/src/content/guide/forms/signals/migration.md b/adev-ja/src/content/guide/forms/signals/migration.md index 0dcea029e..8e592be1d 100644 --- a/adev-ja/src/content/guide/forms/signals/migration.md +++ b/adev-ja/src/content/guide/forms/signals/migration.md @@ -449,3 +449,68 @@ bootstrapApplication(App, { ], }); ``` + +## Custom Controls + +Any [custom Signal Form Control](guide/forms/signals/custom-controls) can be +used with Reactive (and Template-Driven) Forms as-is. This allows you to +migrate existing `ControlValueAccessor` implementations to +`FormValueControl`/`FormCheckboxControl` without breaking existing usages. + +IMPORTANT: Do **not** implement both `ControlValueAccessor` and +`FormValueControl`/`FormCheckboxControl` on the same component. Implement one or +the other. + +Given the following custom control: + +```angular-ts +import {Component, model} from '@angular/core'; +import {FormValueControl} from '@angular/forms/signals'; + +@Component({ + selector: 'app-basic-input', + template: ` +
+ +
+ `, +}) +export class BasicInput implements FormValueControl { + /** The current input value */ + value = model(''); +} +``` + +You can use this custom control with reactive forms as you would a native input +or a custom control based on `ControlValueAccessor`. For example, consider this +simple component with a Reactive Form. + +```angular-ts +import {Component} from '@angular/core'; +import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms'; +import {BasicInput} from './basic-input'; + +@Component({ + selector: 'app-example', + template: ` +
+ + +

Text: {{ reactiveFormGroup.value.reactiveControlName }}

+ `, + imports: [ReactiveFormsModule], +}) +export class ExampleComponent { + readonly reactiveFormGroup = new FormGroup({ + reactiveControlName: new FormControl(''), + }); +} +``` + +Any change to the custom `app-basic-input` control will be reflected in the +reactive `FormControl`. diff --git a/adev-ja/src/content/guide/forms/signals/testing.md b/adev-ja/src/content/guide/forms/signals/testing.md new file mode 100644 index 000000000..2263a009a --- /dev/null +++ b/adev-ja/src/content/guide/forms/signals/testing.md @@ -0,0 +1,214 @@ +# Testing Signal Forms + +Forms are often critical to applications, and testing gives you confidence they behave correctly as the codebase changes. Signal Forms keeps most of its logic in the schema rather than the template, which means you can test the majority of form behavior without rendering a component. + +This guide walks through how to set up those tests, starting with isolated logic tests and then covering component-bound tests for cases where DOM interaction matters. + +## Testing form logic in isolation + +When you only need to verify validation, disabled state, required state, or error output, test the form directly instead of rendering a component. Isolated tests keep the setup small and let the test focus on the form's behavior. + +The key requirement is the injector. Signal Forms needs an injection context during form creation. If a test calls `form()` without one, the call throws before the test can assert anything about the form. + +The most direct way to satisfy this requirement is to pass an injector explicitly. The following test creates a form with a `required` rule and verifies that the field becomes valid after receiving a value: + +```ts {header: 'profile-form.spec.ts'} +import {Injector, signal} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; +import {form, required} from '@angular/forms/signals'; +import {describe, expect, it} from 'vitest'; + +describe('profile form', () => { + it('marks required fields as invalid until they have a value', () => { + const model = signal({name: ''}); + + const profileForm = form( + model, + (path) => { + required(path.name); + }, + {injector: TestBed.inject(Injector)}, + ); + + expect(profileForm.name().valid()).toBe(false); + expect(profileForm.name().errors()).toEqual([expect.objectContaining({kind: 'required'})]); + + profileForm.name().value.set('Ada'); + + expect(profileForm.name().valid()).toBe(true); + expect(profileForm.name().errors()).toEqual([]); + }); +}); +``` + +This pattern works well for most isolated tests because the injector requirement stays visible at the call site. It also mirrors how Signal Forms unit tests in Angular's source create forms. + +When the code under test calls `form()` internally, you may not be able to pass the injector directly. In that case, wrap the call in an ambient injection context: + +```ts {header: 'profile-form.spec.ts'} +import {signal} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; +import {form, required} from '@angular/forms/signals'; +import {describe, expect, it} from 'vitest'; + +describe('profile form', () => { + it('can create a form inside an injection context', () => { + const model = signal({name: ''}); + + TestBed.runInInjectionContext(() => { + const profileForm = form(model, (path) => { + required(path.name); + }); + + expect(profileForm.name().valid()).toBe(false); + }); + }); +}); +``` + +Both patterns produce the same kind of form. Passing `{injector}` is often the clearest choice when the test creates the form directly. `TestBed.runInInjectionContext()` is useful when the code under test calls `form()` internally and you need to supply the surrounding injection context. + +Once the form exists, test it through field state signals. Common assertions include `valid()`, `invalid()`, `disabled()`, `required()`, and `errors()`. For most form logic, that is enough to verify the behavior without involving the DOM. + +## Testing a form with multiple rules + +After the injector setup is in place, a good next step is a complete test that exercises a few pieces of form logic together. This kind of test is still isolated, but it looks much closer to a real application form. + +For example, this test verifies both a basic required rule and a conditional required rule that depends on another field: + +```ts {header: 'profile-form.spec.ts'} +import {Injector, signal} from '@angular/core'; +import {TestBed} from '@angular/core/testing'; +import {form, required} from '@angular/forms/signals'; +import {describe, expect, it} from 'vitest'; + +describe('profile form', () => { + it('updates validation state when related fields change', () => { + const model = signal({ + name: '', + age: 5, + }); + + const profileForm = form( + model, + (path) => { + required(path.name); + required(path.name, { + error: (ctx) => ({kind: `required-${ctx.valueOf(path.age)}`}), + when: ({valueOf}) => valueOf(path.age) > 10, + }); + }, + {injector: TestBed.inject(Injector)}, + ); + + expect(profileForm.name().invalid()).toBe(true); + expect(profileForm.name().errors()).toEqual([expect.objectContaining({kind: 'required'})]); + + profileForm.age().value.set(15); + + expect(profileForm.name().errors()).toEqual([ + expect.objectContaining({kind: 'required'}), + expect.objectContaining({kind: 'required-15'}), + ]); + + profileForm.name().value.set('Ada'); + + expect(profileForm.name().valid()).toBe(true); + expect(profileForm.name().errors()).toEqual([]); + }); +}); +``` + +This example shows an important testing pattern: update one field, then assert against the state of another field. Because Signal Forms rules are reactive, a field's validation can depend on sibling values, parent values, or other derived conditions. Tests should verify those relationships directly instead of only checking the field that changed. + +For validation-focused tests, `errors()` is usually the most useful assertion. `valid()` and `invalid()` tell you whether the field currently passes validation, but `errors()` shows which rule produced the failure. That becomes especially useful once a field has multiple validators or conditional rules. + +The same structure works for most everyday form tests: + +1. Create a model signal with the smallest shape that reproduces the behavior. +1. Build the form with an explicit injector. +1. Assert the initial field state. +1. Change a field with `.value.set(...)`, including sibling fields when testing cross-field rules. +1. Assert the updated state signals, usually `errors()`, `valid()`, or `invalid()`. + +When a test is about schema behavior rather than rendering, default to this isolated style. It is faster than a component test and makes it easier to see which rule is responsible when the behavior changes. + +## Testing forms bound to components + +When you need to verify behavior that depends on template bindings, user interaction through `dispatchEvent`, or custom form controls that manage their own rendering, isolated tests are not enough. You need component-bound tests to render the template so that you can interact with actual DOM elements. + +### Setting up a component test + +Component-bound tests render the component so you can interact with actual DOM elements. Create the component with `TestBed.createComponent()` and wait for rendering to complete before asserting: + +```angular-ts {header: 'profile-form.ts'} +import {Component, signal} from '@angular/core'; +import {form, FormField, required} from '@angular/forms/signals'; + +@Component({ + selector: 'app-profile-form', + imports: [FormField], + template: ``, +}) +export class ProfileForm { + readonly model = signal({name: 'Ada'}); + readonly profileForm = form(this.model, (path) => { + required(path.name); + }); +} +``` + +```ts {header: 'profile-form.spec.ts'} +import {TestBed} from '@angular/core/testing'; +import {describe, expect, it} from 'vitest'; +import {ProfileForm} from './profile-form'; + +describe('ProfileForm', () => { + it('reflects model values in the DOM and updates the model on user input', async () => { + const fixture = TestBed.createComponent(ProfileForm); + await fixture.whenStable(); + + const input = fixture.nativeElement.querySelector('input') as HTMLInputElement; + + // Model → View: the input reflects the model's initial value + expect(input.value).toBe('Ada'); + + // View → Model: simulate the user clearing the field + input.value = ''; + input.dispatchEvent(new Event('input')); + await fixture.whenStable(); + + expect(fixture.componentInstance.profileForm.name().value()).toBe(''); + expect(fixture.componentInstance.profileForm.name().valid()).toBe(false); + }); +}); +``` + +Notice that the component uses `form()` without an explicit injector because the component's own injection context provides it automatically. After each change, `await fixture.whenStable()` waits for rendering and effects to complete before asserting. + +The same pattern works for async operations such as async validators or server calls. Call `await fixture.whenStable()` after the async work resolves. + +## When to use each approach + +| What you need to verify | Approach | +| ---------------------------------------------------- | --------------- | +| Validation rules, `errors()`, `valid()`, `invalid()` | Isolated | +| Disabled, required, or readonly state | Isolated | +| Cross-field reactive dependencies | Isolated | +| Conditional schemas (`applyWhen`, `applyWhenValue`) | Isolated | +| Input values rendering in the DOM | Component-bound | +| User typing updating the model | Component-bound | +| Custom form controls with their own templates | Component-bound | +| Focus management or accessibility attributes | Component-bound | + +Most forms only need isolated tests. The form's logic (such as validation, disabled state, cross-field rules) lives in the schema, and schemas do not need a template to run. Component-bound tests add value when the behavior you care about crosses the boundary between the form and the DOM. + +## Next steps + +This guide covered testing Signal Forms in isolation and with component templates. Here are related guides that explore other aspects of Signal Forms: + + + + + + diff --git a/adev-ja/src/content/guide/http/http-resource.md b/adev-ja/src/content/guide/http/http-resource.md index a20d69199..bb045cc23 100644 --- a/adev-ja/src/content/guide/http/http-resource.md +++ b/adev-ja/src/content/guide/http/http-resource.md @@ -6,7 +6,7 @@ For more about Angular's `resource` pattern, see [Async reactivity with `resourc ## `Using httpResource` -TIP: Make sure to include `provideHttpClient` in your application providers. See [Setting up HttpClient](/guide/http/setup) for details. +TIP: `httpResource` uses the globally available `HttpClient`. Use `provideHttpClient(...)` only when you need to configure HTTP features, such as interceptors or XSRF options. See [Setting up HttpClient](/guide/http/setup) for details. You can define an HTTP resource by returning a url: @@ -16,7 +16,7 @@ userId = input.required(); user = httpResource(() => `/api/user/${userId()}`); // A reactive function as argument ``` -`httpResource` is reactive, meaning that whenever one of the signal it depends on changes (like `userId`), the resource will emit a new http request. +`httpResource` is reactive, meaning that whenever one of the signals it depends on changes (like `userId`), the resource will emit a new http request. If a request is already pending, the resource cancels the outstanding request before issuing a new one. HELPFUL: `httpResource` differs from the `HttpClient` as it initiates the request _eagerly_. In contrast, the `HttpClient` only initiates requests upon subscription to the returned `Observable`. @@ -53,8 +53,8 @@ TIP: Avoid using `httpResource` for _mutations_ like `POST` or `PUT`. Instead, p The signals of the `httpResource` can be used in the template to control which elements should be displayed. ```angular-html -@if(user.hasValue()) { - +@if (user.hasValue()) { + } @else if (user.error()) {
Could not load user information
} @else if (user.isLoading()) { @@ -66,7 +66,7 @@ HELPFUL: Reading the `value` signal on a `resource` that is in error state throw ### Response types -By default, `httpResource` returns and parses the response as JSON. However, you can specify alternate return with additional functions on `httpResource`: +By default, `httpResource` returns and parses the response as JSON. However, you can specify an alternate return type with additional functions on `httpResource`: ```ts httpResource.text(() => ({ 
 })); // returns a string in value() @@ -107,7 +107,7 @@ The following example shows a unit test for code using `httpResource`. ```ts TestBed.configureTestingModule({ - providers: [provideHttpClient(), provideHttpClientTesting()], + providers: [provideHttpClientTesting()], }); const id = signal(0); 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 3565cf2ef..122f32b6f 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: 'USD' : 'symbol' : '1.2-2' : '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/i18n/prepare.md b/adev-ja/src/content/guide/i18n/prepare.md index b2fc6175b..225e130d4 100644 --- a/adev-ja/src/content/guide/i18n/prepare.md +++ b/adev-ja/src/content/guide/i18n/prepare.md @@ -403,5 +403,5 @@ The following code example shows nested clauses based on the `gender` and `minut [GithubAngularAngularBlobEcffc3557fe1bff9718c01277498e877ca44588dPackagesCoreSrcI18nLocaleEnTsL14L18]: https://github.com/angular/angular/blob/ecffc3557fe1bff9718c01277498e877ca44588d/packages/core/src/i18n/locale_en.ts#L14-L18 'Line 14 to 18 - angular/packages/core/src/i18n/locale_en.ts | angular/angular | GitHub' [GithubUnicodeOrgIcuUserguideFormatParseMessages]: https://unicode-org.github.io/icu/userguide/format_parse/messages 'ICU Message Format - ICU Documentation | Unicode | GitHub' [UnicodeCldrMain]: https://cldr.unicode.org 'Unicode CLDR Project' -[UnicodeCldrIndexCldrSpecPluralRules]: http://cldr.unicode.org/index/cldr-spec/plural-rules 'Plural Rules | CLDR - Unicode Common Locale Data Repository | Unicode' -[UnicodeCldrIndexCldrSpecPluralRulesTocChoosingPluralCategoryNames]: http://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Choosing-Plural-Category-Names 'Choosing Plural Category Names - Plural Rules | CLDR - Unicode Common Locale Data Repository | Unicode' +[UnicodeCldrIndexCldrSpecPluralRules]: https://cldr.unicode.org/index/cldr-spec/plural-rules 'Plural Rules | CLDR - Unicode Common Locale Data Repository | Unicode' +[UnicodeCldrIndexCldrSpecPluralRulesTocChoosingPluralCategoryNames]: https://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Choosing-Plural-Category-Names 'Choosing Plural Category Names - Plural Rules | CLDR - Unicode Common Locale Data Repository | Unicode' diff --git a/adev-ja/src/content/guide/i18n/translation-files.md b/adev-ja/src/content/guide/i18n/translation-files.md index 45782f9b4..e684539b0 100644 --- a/adev-ja/src/content/guide/i18n/translation-files.md +++ b/adev-ja/src/content/guide/i18n/translation-files.md @@ -238,6 +238,6 @@ The following example displays both translation units after translating. [GithubUnicodeOrgCldrStagingChartsLatestSupplementalLanguagePluralRulesHtml]: https://cldr.unicode.org/index/cldr-spec/plural-rules 'Language Plural Rules - CLDR Charts | Unicode | GitHub' [JsonMain]: https://www.json.org 'Introducing JSON | JSON' [OasisOpenDocsXliffXliffCoreXliffCoreHtml]: https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html 'XLIFF Version 1.2 Specification | Oasis Open Docs' -[OasisOpenDocsXliffXliffCoreV20Cos01XliffCoreV20Cose01Html]: http://docs.oasis-open.org/xliff/xliff-core/v2.0/cos01/xliff-core-v2.0-cos01.html 'XLIFF Version 2.0 | Oasis Open Docs' -[UnicodeCldrDevelopmentDevelopmentProcessDesignProposalsXmb]: http://cldr.unicode.org/development/development-process/design-proposals/xmb 'XMB | CLDR - Unicode Common Locale Data Repository | Unicode' +[OasisOpenDocsXliffXliffCoreV20Cos01XliffCoreV20Cose01Html]: https://docs.oasis-open.org/xliff/xliff-core/v2.0/cos01/xliff-core-v2.0-cos01.html 'XLIFF Version 2.0 | Oasis Open Docs' +[UnicodeCldrDevelopmentDevelopmentProcessDesignProposalsXmb]: https://cldr.unicode.org/development/development-process/design-proposals/xmb 'XMB | CLDR - Unicode Common Locale Data Repository | Unicode' [WikipediaWikiXliff]: https://en.wikipedia.org/wiki/XLIFF 'XLIFF | Wikipedia' diff --git a/adev-ja/src/content/guide/signals/debounced.md b/adev-ja/src/content/guide/signals/debounced.md index 146b798e0..0d424d964 100644 --- a/adev-ja/src/content/guide/signals/debounced.md +++ b/adev-ja/src/content/guide/signals/debounced.md @@ -35,7 +35,7 @@ export class Search { ## Status during debounce -While the debounce timer is counting down, `status()` is `'loading'` and `value()` returns the previously resolved value. When the timer expires, the resource settles to `'resolved'`. If the source signal throws, the resource enters `'error'` immediately no timer runs. +While the debounce timer is counting down, `status()` is `'loading'` and `value()` returns the previously resolved value. When the timer expires, the resource settles to `'resolved'`. If the source signal throws, the resource enters `'error'` immediately; no timer runs. See [Resource status](/guide/signals/resource#resource-status) for the full list of statuses and their `value()` behavior. diff --git a/adev-ja/src/content/guide/signals/effect.md b/adev-ja/src/content/guide/signals/effect.md index 0f02c3558..0acefd12e 100644 --- a/adev-ja/src/content/guide/signals/effect.md +++ b/adev-ja/src/content/guide/signals/effect.md @@ -21,7 +21,7 @@ Effects should be the last API you reach for. Always prefer `computed()` for der TIP: There are no situations where effect is good, only situations where it is appropriate. - Logging signal values, either for analytics or as a debugging tool. -- Keeping data in sync with different kind of storages: `window.localStorage`, session storage, cookies etc. +- Keeping data in sync with different kinds of storage: `window.localStorage`, session storage, cookies, etc. - Adding custom DOM behavior that can't be expressed with template syntax. - Performing custom rendering to a `` element, charting library, or other third party UI library. @@ -36,9 +36,7 @@ Instead, use `computed` signals to model state that depends on other state. By default, you can only create an `effect()` within an [injection context](guide/di/dependency-injection-context) (where you have access to the `inject` function). The easiest way to satisfy this requirement is to call `effect` within a component, directive, or service `constructor`: ```ts -@Component({ - /*...*/ -}) +@Component(/* ... */) export class EffectiveCounter { readonly count = signal(0); @@ -54,9 +52,7 @@ export class EffectiveCounter { To create an effect outside the constructor, you can pass an `Injector` to `effect` via its options: ```ts -@Component({ - /*...*/ -}) +@Component(/* ... */) export class EffectiveCounter { readonly count = signal(0); private injector = inject(Injector); @@ -123,9 +119,7 @@ The `effect` function is a general-purpose tool for running code in reaction to For these situations, you can use `afterRenderEffect`. It functions like `effect`, but runs after Angular has finished rendering and committed its changes to the DOM. ```ts -@Component({ - /*...*/ -}) +@Component(/* ... */) export class MyFancyChart { chartData = input.required(); canvas = viewChild.required>('canvas'); diff --git a/adev-ja/src/content/guide/testing/services.md b/adev-ja/src/content/guide/testing/services.md index 1da612b18..99ea12829 100644 --- a/adev-ja/src/content/guide/testing/services.md +++ b/adev-ja/src/content/guide/testing/services.md @@ -151,6 +151,10 @@ describe('OrderTotal', () => { service = TestBed.inject(OrderTotal); }); + afterEach(() => { + taxCalculatorStub.calculate.mockClear(); + }); + it('adds tax to the subtotal', () => { expect(service.total(100)).toBe(105); }); @@ -158,7 +162,7 @@ describe('OrderTotal', () => { // Verify the interaction with a spy it('calls the tax calculator', () => { service.total(100); - expect(taxCalculatorStub.calculate).toHaveBeenCalledExactlyOnce(); + expect(taxCalculatorStub.calculate).toHaveBeenCalledExactlyOnceWith(100); }); }); ``` 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 88ab09179..b141b3547 100644 --- a/adev-ja/src/content/reference/configs/angular-compiler-options.md +++ b/adev-ja/src/content/reference/configs/angular-compiler-options.md @@ -201,8 +201,7 @@ When `true`, enables [strict template type checking](tools/cli/template-typechec The strictness flags that this option enables allow you to turn on and off specific types of strict template type checking. See [troubleshooting template errors](tools/cli/template-typecheck#troubleshooting-template-errors). - -When you use the Angular CLI command `ng new --strict`, it is set to `true` in the new project's configuration. +Default is `true`. ### `strictStandalone` diff --git a/adev-ja/src/content/reference/configs/workspace-config.md b/adev-ja/src/content/reference/configs/workspace-config.md index 322310abd..d8c51180d 100644 --- a/adev-ja/src/content/reference/configs/workspace-config.md +++ b/adev-ja/src/content/reference/configs/workspace-config.md @@ -245,6 +245,7 @@ For details of those options and their possible values, see the [Angular CLI Ref | `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. | +| `inlineStyleLanguage` | The stylesheet language to use for the application's inline component styles. Accepts `css`, `less`, `sass`, or `scss`, and defaults to `css`. | | `scripts` | An object containing JavaScript files to add to the application. The scripts are loaded exactly as if you had added them in a `