From 2ae0d3432bf6422e5e076a5bfa71bbceb3e8d0c4 Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 25 Oct 2026 10:05:11 +0900 Subject: [PATCH 01/51] chore: update origin to 4e4df4a --- origin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/origin b/origin index ef0359676..4e4df4a69 160000 --- a/origin +++ b/origin @@ -1 +1 @@ -Subproject commit ef0359676c8e25e363ee39e8060cf13933cfe1d9 +Subproject commit 4e4df4a696aa426885af44afbcb13ee58014455d From 9291494954fcefcdecea2132ad2e8f1dd968ecec Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 25 Oct 2026 10:05:46 +0900 Subject: [PATCH 02/51] fix: migrate untranslated files --- adev-ja/src/content/ai/mcp-server-setup.md | 12 ++-- .../ecosystem/rxjs-interop/output-interop.md | 4 +- .../ecosystem/rxjs-interop/signals-interop.md | 7 +- .../rxjs-interop/take-until-destroyed.md | 1 + .../service-workers/getting-started.md | 2 + .../di/debugging-and-troubleshooting-di.md | 43 ++++++------ .../guide/di/defining-dependency-providers.md | 5 +- .../guide/forms/signals/async-operations.md | 5 +- .../forms/signals/dynamic-forms-with-json.md | 16 +++-- .../guide/forms/signals/field-metadata.md | 3 +- .../content/guide/forms/signals/form-logic.md | 2 +- .../guide/forms/signals/form-submission.md | 6 +- .../content/guide/forms/signals/migration.md | 20 +++++- .../content/guide/forms/signals/schemas.md | 4 +- adev-ja/src/content/guide/i18n/deploy.md | 3 +- adev-ja/src/content/guide/i18n/merge.md | 2 +- adev-ja/src/content/guide/i18n/overview.md | 3 +- .../src/content/guide/ngmodules/overview.md | 1 + .../src/content/guide/signals/debounced.md | 2 +- adev-ja/src/content/guide/testing/services.md | 2 +- .../src/content/reference/errors/NG01055.md | 39 +++++++++++ .../src/content/reference/errors/NG01354.md | 2 + .../src/content/reference/errors/NG02825.md | 1 + .../src/content/reference/errors/NG05101.md | 10 +-- .../src/content/reference/errors/NG05201.md | 52 ++++++--------- .../src/content/reference/errors/NG0904.md | 66 +++++++++++++++++++ .../src/content/reference/errors/NG0991.md | 1 + .../reference/extended-diagnostics/NG8103.md | 4 +- .../reference/migrations/inject-function.md | 14 ++-- .../migrations/route-lazy-loading.md | 2 + .../router-testing-module-migration.md | 6 +- .../reference/migrations/signal-queries.md | 8 +-- .../reference/migrations/standalone.md | 2 +- adev-ja/src/content/reference/press-kit.md | 6 +- .../content/tools/cli/aot-metadata-errors.md | 5 +- adev-ja/src/content/tools/cli/end-to-end.md | 8 +-- .../content/tools/cli/template-typecheck.md | 32 +++++---- adev-ja/src/content/tools/devtools/signals.md | 60 +++++++++++++++++ adev-ja/src/content/tools/language-service.md | 4 +- .../first-app/steps/09-services/README.md | 6 +- .../first-app/steps/11-details-page/README.md | 2 +- .../first-app/steps/14-http/README.md | 2 +- 42 files changed, 336 insertions(+), 139 deletions(-) create mode 100644 adev-ja/src/content/reference/errors/NG01055.md create mode 100644 adev-ja/src/content/reference/errors/NG0904.md create mode 100644 adev-ja/src/content/tools/devtools/signals.md diff --git a/adev-ja/src/content/ai/mcp-server-setup.md b/adev-ja/src/content/ai/mcp-server-setup.md index f8901dd2f..2d96fd5f2 100644 --- a/adev-ja/src/content/ai/mcp-server-setup.md +++ b/adev-ja/src/content/ai/mcp-server-setup.md @@ -67,9 +67,9 @@ When the MCP server is enabled, AI agents have access to the following tools: | Name | Description | | :-------------------------- | :-------------------------------------------------------------------------------------------------------- | | `ai_tutor` | Launches an interactive AI-powered Angular tutor. | -| `devserver.start` | Asynchronously starts a dev server (`ng serve`). Returns immediately. | -| `devserver.stop` | Stops the dev server. | -| `devserver.wait_for_build` | Returns the logs of the most recent build in a running dev server. | +| `devserver_start` | Asynchronously starts a dev server (`ng serve`). Returns immediately. | +| `devserver_stop` | Stops the dev server. | +| `devserver_wait_for_build` | Returns the logs of the most recent build in a running dev server. | | `get_best_practices` | Retrieves the Angular Best Practices Guide (crucial for standalone components, typed forms, etc.). | | `list_projects` | Lists all applications and libraries in the workspace by reading `angular.json`. | | `onpush_zoneless_migration` | Analyzes code and provides a plan to migrate it to `OnPush` change detection (prerequisite for zoneless). | @@ -97,10 +97,10 @@ The AI agent automates research, implementation, and verification when developin 1. **API & Syntax Research**: The AI agent uses `search_documentation` to look up Angular APIs or syntax rules (e.g., `@defer` block options). 2. **Load Coding Standards**: The AI agent calls `get_best_practices` with the workspace path to load Angular version-aligned coding rules. -3. **Start Local Dev Server**: The AI agent starts a background server by calling `devserver.start`. -4. **Monitor Build**: The AI agent uses `devserver.wait_for_build` to watch build logs and ensure compilation succeeds as it edits the code. +3. **Start Local Dev Server**: The AI agent starts a background server by calling `devserver_start`. +4. **Monitor Build**: The AI agent uses `devserver_wait_for_build` to watch build logs and ensure compilation succeeds as it edits the code. 5. **Write and Execute Tests**: The AI agent identifies the project's test framework (e.g., Jasmine, Jest, Vitest) via `list_projects`, writes the corresponding test file, and runs the tests using `run_target` with `"test"`. -6. **Stop Dev Server**: When finished, the AI agent stops the active dev server by calling `devserver.stop`. +6. **Stop Dev Server**: When finished, the AI agent stops the active dev server by calling `devserver_stop`. ### 3. Developer Onboarding and Learning 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 d2fe7e59e..75f6165f7 100644 --- a/adev-ja/src/content/ecosystem/rxjs-interop/output-interop.md +++ b/adev-ja/src/content/ecosystem/rxjs-interop/output-interop.md @@ -4,13 +4,14 @@ TIP: This guide assumes you're familiar with [component and directive outputs](g The `@angular/core/rxjs-interop` package offers two APIs related to component and directive outputs. -## Creating an output based on an RxJs Observable +## Creating an output based on an RxJS Observable The `outputFromObservable` lets you create a component or directive output that emits based on an RxJS observable: ```ts {highlight:[9]} import {Directive} from '@angular/core'; import {outputFromObservable} from '@angular/core/rxjs-interop'; +import {Observable} from 'rxjs'; @Directive(/* ... */) class Draggable { @@ -33,6 +34,7 @@ The `outputToObservable` function lets you create an RxJS observable from a comp ```ts {highlight:[11]} import {outputToObservable} from '@angular/core/rxjs-interop'; +import {Component, output} from '@angular/core'; @Component(/*...*/) class CustomSlider { 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 db1ff3699..f7f96e338 100644 --- a/adev-ja/src/content/ecosystem/rxjs-interop/signals-interop.md +++ b/adev-ja/src/content/ecosystem/rxjs-interop/signals-interop.md @@ -2,7 +2,7 @@ The `@angular/core/rxjs-interop` package offers APIs that help you integrate RxJS and Angular signals. -## Create a signal from an RxJs Observable with `toSignal` +## Create a signal from an RxJS Observable with `toSignal` 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. @@ -91,8 +91,9 @@ If an Observable used in `toSignal` completes, the signal continues to return th Use the `toObservable` utility to create an `Observable` which tracks the value of a signal. The signal's value is monitored with an `effect` which emits the value to the Observable when it changes. ```ts -import {Component, signal} from '@angular/core'; +import {Component, signal, Signal, inject} from '@angular/core'; import {toObservable} from '@angular/core/rxjs-interop'; +import {switchMap} from 'rxjs'; @Component(/* ... */) export class SearchResults { @@ -131,7 +132,7 @@ Here, only the last value (3) will be logged. Angular's [`resource` function](/guide/signals/resource) gives you a way to incorporate async data into your application's signal-based code. Building on top of this pattern, `rxResource` lets you define a resource where the source of your data is defined in terms of an RxJS `Observable`. Instead of accepting a `loader` function, `rxResource` accepts a `stream` function that returns an RxJS `Observable`. ```typescript -import {Component, inject} from '@angular/core'; +import {Component, inject, input} from '@angular/core'; import {rxResource} from '@angular/core/rxjs-interop'; @Component(/* ... */) diff --git a/adev-ja/src/content/ecosystem/rxjs-interop/take-until-destroyed.md b/adev-ja/src/content/ecosystem/rxjs-interop/take-until-destroyed.md index fc94b13d5..00179aa5f 100644 --- a/adev-ja/src/content/ecosystem/rxjs-interop/take-until-destroyed.md +++ b/adev-ja/src/content/ecosystem/rxjs-interop/take-until-destroyed.md @@ -7,6 +7,7 @@ The `takeUntilDestroyed` operator, from `@angular/core/rxjs-interop`, provides a ```typescript import {Component, inject} from '@angular/core'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {Observable} from 'rxjs'; import {NotificationDispatcher, CustomPopupShower} from './some-shared-project-code'; @Component(/* ... */) diff --git a/adev-ja/src/content/ecosystem/service-workers/getting-started.md b/adev-ja/src/content/ecosystem/service-workers/getting-started.md index 26c2bfc8d..bcd7908ca 100644 --- a/adev-ja/src/content/ecosystem/service-workers/getting-started.md +++ b/adev-ja/src/content/ecosystem/service-workers/getting-started.md @@ -285,6 +285,8 @@ export const delayedConfig: ApplicationConfig = { You can also provide an Observable factory function for custom registration timing: ```ts +import {ApplicationConfig, isDevMode} from '@angular/core'; +import {provideServiceWorker} from '@angular/service-worker'; import {timer} from 'rxjs'; export const customConfig: ApplicationConfig = { 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 581f3b726..8407acc00 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 @@ -148,7 +148,7 @@ export class UserClient { Sometimes you want separate instances per component for component-specific state. ```angular-ts {header: 'Intentional: Component-scoped state'} -import {Injectable, signal} from '@angular/core'; +import {Injectable, signal, Component, inject} from '@angular/core'; @Injectable() // No providedIn - must be provided explicitly export class FormStateStore { @@ -390,6 +390,7 @@ export const APP_CONFIG = new InjectionToken('app config'); ``` ```ts {header: 'app.config.ts'} +import {bootstrapApplication} from '@angular/platform-browser'; import {APP_CONFIG} from './config.token'; export const appConfig: AppConfig = { @@ -403,7 +404,7 @@ bootstrapApplication(App, { ```angular-ts {avoid, header: 'feature-view.ts'} // Creating new token with same description -import {InjectionToken, inject} from '@angular/core'; +import {InjectionToken, inject, Component} from '@angular/core'; import {AppConfig} from './config.token'; const APP_CONFIG = new InjectionToken('app config'); @@ -422,7 +423,7 @@ Even though both tokens have the description `'app config'`, they are different **Solution:** Import the same token instance. ```angular-ts {prefer, header: 'feature-view.ts'} -import {inject} from '@angular/core'; +import {inject, Component} from '@angular/core'; import {APP_CONFIG, AppConfig} from './config.token'; @Component({ @@ -459,7 +460,8 @@ export class UserProfile { **Solution:** Use `InjectionToken` for interface types. ```angular-ts {prefer, header: 'Use InjectionToken for interfaces'} -import {InjectionToken, inject} from '@angular/core'; +import {InjectionToken, inject, Component} from '@angular/core'; +import {bootstrapApplication} from '@angular/platform-browser'; interface UserConfig { name: string; @@ -676,11 +678,10 @@ This section provides detailed information about specific Angular DI error codes 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 +NG0201: No provider found for `UserClient`. Path: AuthClient -> UserClient. ``` -The dependency path shows that `App` injected `AuthClient`, which tried to inject `UserClient`, but no provider was found. +The dependency path shows that `AuthClient` tried to inject `UserClient`, but no provider was found. #### Missing the `@Service ` or `@Injectable` decorator @@ -749,7 +750,7 @@ import {UserClient} from './user-client'; @Component({ selector: 'app-profile', - template: '

User: {{user().name}}

', + template: '

User: {{user.name}}

', }) export class UserProfile { private userService = inject(UserClient); // ERROR: No provider @@ -765,7 +766,7 @@ import {UserClient} from './user-client'; @Component({ selector: 'app-profile', - template: '

User: {{user().name}}

', + template: '

User: {{user.name}}

', }) export class UserProfile { private userService = inject(UserClient); // Works: providedIn: 'root' @@ -778,16 +779,14 @@ export class UserProfile { 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 +NG0201: No provider found for `LoggerStore`. Path: 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 +1. `DataStore` injected `ApiClient` +2. `ApiClient` tried to inject `LoggerStore` +3. No provider for `LoggerStore` was found Start your investigation at the end of the chain (`LoggerStore`) and verify it has proper configuration. @@ -818,9 +817,10 @@ Optional injection returns `null` if no provider is found, allowing you to handl 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`. +NG0203: The `UserClient` token injection failed. `inject()` function 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 @@ -835,7 +835,7 @@ Angular allows `inject()` in these locations: @Component({ selector: 'app-profile', - template: '

User: {{user().name}}

', + template: '

User: {{user.name}}

', }) export class UserProfile { private userService = inject(UserClient); // Valid @@ -851,7 +851,7 @@ Angular allows `inject()` in these locations: @Component({ selector: 'app-profile', - template: '

User: {{user().name}}

', + template: '

User: {{user.name}}

', }) export class UserProfile { private userService: UserClient; @@ -963,8 +963,7 @@ setTimeout(() => { 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 +NG0200: Circular dependency detected for `AuthClient`. Path: AuthClient -> UserClient -> AuthClient. ``` The dependency path shows the cycle: `AuthClient` depends on `UserClient`, which depends back on `AuthClient`. 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 5784af27f..3d620f02b 100644 --- a/adev-ja/src/content/guide/di/defining-dependency-providers.md +++ b/adev-ja/src/content/guide/di/defining-dependency-providers.md @@ -39,7 +39,7 @@ NOTE: The string parameter (e.g., `'api.url'`) is a description purely for debug An `InjectionToken` that has a `factory` results in `providedIn: 'root'` by default (but can be overridden via the `providedIn` prop). ```ts {header: "/app/config.token.ts"} -import {InjectionToken} from '@angular/core'; +import {InjectionToken, Component, inject} from '@angular/core'; export interface AppConfig { apiUrl: string; @@ -548,7 +548,7 @@ class ApiClient { } // Factory function that configures based on user tier -import {inject} from '@angular/core'; +import {inject, Component} from '@angular/core'; import {HttpClient} from '@angular/common/http'; const apiClientFactory = () => { const http = inject(HttpClient); @@ -786,6 +786,7 @@ For more complex scenarios, you can combine multiple configuration approaches: ```ts {header: "/libs/http-client/src/provider.ts"} import {Provider, InjectionToken, inject} from '@angular/core'; +import {bootstrapApplication} from '@angular/platform-browser'; // Feature flags for optional functionality export enum HttpFeatures { 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 9c318793a..11c25911b 100644 --- a/adev-ja/src/content/guide/forms/signals/async-operations.md +++ b/adev-ja/src/content/guide/forms/signals/async-operations.md @@ -215,6 +215,7 @@ Customize the HTTP request by returning an `HttpResourceRequest` object from the ```ts import {HttpHeaders} from '@angular/common/http'; +import {validateHttp} from '@angular/forms/signals'; validateHttp(schemaPath.field, { request: ({value}) => ({ @@ -445,7 +446,7 @@ With `'blur'`, the model keeps its previous value while the user is typing. Sync For timing logic that a duration or `'blur'` can't express, pass a `Debouncer` function. The function receives the field context and an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal), and returns a `Promise` that resolves when the model should update: ```ts -import {debounce, type Debouncer} from '@angular/forms/signals'; +import {debounce, type Debouncer, form} from '@angular/forms/signals'; const shorterWhenLonger: Debouncer = ({value}, abortSignal) => { // Shorter queries get a longer delay since the user is likely still typing. @@ -635,6 +636,7 @@ Async validation only runs after synchronous validation passes. This prevents un ```ts import {form, required, minLength, validateHttp} from '@angular/forms/signals'; +import {model} from '@angular/core'; form(model, (schemaPath) => { // 1. These synchronous validation rules run first @@ -673,6 +675,7 @@ Always validate format before making async requests. This catches errors instant ```ts import {form, required, email, validateHttp} from '@angular/forms/signals'; +import {model} from '@angular/core'; form(model, (schemaPath) => { // Validate format first 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 index 5076120b6..0f3f71e8f 100644 --- 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 @@ -61,7 +61,7 @@ In addition, numeric fields initialize to `null` rather than `0` so an empty fie 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'; +import {required, min, max, SchemaFn, SchemaPath} from '@angular/forms/signals'; function buildSchema(configs: FieldConfig[]): SchemaFn> { return (path) => { @@ -73,8 +73,9 @@ function buildSchema(configs: FieldConfig[]): SchemaFn; + if (config.min !== undefined) min(numberPath, config.min); + if (config.max !== undefined) max(numberPath, config.max); } } }; @@ -106,7 +107,7 @@ type FieldConfig = 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'; +import {applyWhen, required, min, max, SchemaFn, SchemaPath} from '@angular/forms/signals'; function buildSchema(configs: FieldConfig[]): SchemaFn> { return (rootPath) => { @@ -115,8 +116,9 @@ function buildSchema(configs: FieldConfig[]): SchemaFn; + if (config.min !== undefined) min(numberPath, config.min); + if (config.max !== undefined) max(numberPath, config.max); } }; @@ -291,7 +293,7 @@ In the template, render an array case like this: } ``` -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. +The `addItem()` method extends the model; the form re-derives the array's fields automatically. New items start with fresh validation state. The `removeItem()` method filters the model. Because arrays of primitives are [tracked by position](#tracking-item-identity), field state is tied to the index rather than the value: when an item is removed, subsequent values shift into earlier positions and inherit their existing field state, while the trailing field is dropped. ### Tracking item identity 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 fee677e44..77fdc8259 100644 --- a/adev-ja/src/content/guide/forms/signals/field-metadata.md +++ b/adev-ja/src/content/guide/forms/signals/field-metadata.md @@ -180,7 +180,8 @@ After both rules run, `state.metadata(HELP)!()` returns only the second message. To combine contributions, pass a reducer to `createMetadataKey()`. A reducer describes how to fold individual values into an accumulated result: ```ts -import {createMetadataKey, MetadataReducer} from '@angular/forms/signals'; +import {createMetadataKey, MetadataReducer, form, metadata} from '@angular/forms/signals'; +import {model} from '@angular/core'; const HELP = createMetadataKey(MetadataReducer.list()); 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 11e6c5d38..f7f15cfa2 100644 --- a/adev-ja/src/content/guide/forms/signals/form-logic.md +++ b/adev-ja/src/content/guide/forms/signals/form-logic.md @@ -695,7 +695,7 @@ The conditional rules only run when the condition is true. This is useful for co Extract common rule configurations into reusable functions: ```ts -import {SchemaPath, debounce, metadata, maxLength} from '@angular/forms/signals'; +import {SchemaPath, debounce, metadata, maxLength, form} from '@angular/forms/signals'; import {PLACEHOLDER} from './metadata-keys'; function emailFieldConfig(path: SchemaPath) { diff --git a/adev-ja/src/content/guide/forms/signals/form-submission.md b/adev-ja/src/content/guide/forms/signals/form-submission.md index 633374844..72012936a 100644 --- a/adev-ja/src/content/guide/forms/signals/form-submission.md +++ b/adev-ja/src/content/guide/forms/signals/form-submission.md @@ -13,7 +13,7 @@ The `submit()` function runs through a specific sequence: 1. **Run the action** — The `action` function executes with the form's current value. While it runs, `submitting()` returns `true`. 1. **Handle the result** — If the action returns errors, they are routed to their target fields. If it returns nothing, the submission is treated as successful. -The `submit()` function returns a `Promise` that resolves to `true` when the action completes without errors, and `false` when validation fails or the action returns errors. +The `submit()` function returns a `Promise` that resolves to `true` when the action completes without errors, and `false` when validation fails, the action returns errors, or a submission is already in progress. If the action throws, the promise rejects with that error. ## Setting up form submission with `FormRoot` @@ -23,7 +23,7 @@ The `FormRoot` directive handles three things automatically when bound to a `` — `true` when the action completes without errors, `false` when validation fails or the action returns errors. Use this to trigger side effects like navigation or notifications. +The `submit()` function returns a `Promise` — `true` when the action completes without errors, `false` when validation fails, the action returns errors, or a submission is already in progress. If the action throws, the promise rejects. Use this to trigger side effects like navigation or notifications. ```ts async onSave() { diff --git a/adev-ja/src/content/guide/forms/signals/migration.md b/adev-ja/src/content/guide/forms/signals/migration.md index 1b21e489a..f9e059b7e 100644 --- a/adev-ja/src/content/guide/forms/signals/migration.md +++ b/adev-ja/src/content/guide/forms/signals/migration.md @@ -68,7 +68,7 @@ In the template, use standard reactive syntax by binding the underlying control: @if (f.password().touched() && f.password().invalid()) {
@for (error of f.password().errors(); track error) { -

{{ error.message || error.kind }}

+

{{ errorMessage(error) }}

}
} @@ -76,6 +76,20 @@ In the template, use standard reactive syntax by binding the underlying control: ``` +Errors from the existing validators are `CompatValidationError`s. They don't set `message`; the error object returned by the validator is available as `context` instead: + +```typescript +import {ValidationError} from '@angular/forms/signals'; +import {CompatValidationError} from '@angular/forms/signals/compat'; + +errorMessage(error: ValidationError) { + if (error instanceof CompatValidationError) { + return (error.context as {message?: string} | null)?.message ?? error.kind; + } + return error.message ?? error.kind; +} +``` + @@ -90,6 +104,7 @@ You can also wrap an entire `FormGroup`. This is common when a reusable sub-sect import {signal} from '@angular/core'; import {FormGroup, FormControl, Validators} from '@angular/forms'; import {compatForm} from '@angular/forms/signals/compat'; +import {required} from '@angular/forms/signals'; // 1. An existing address group with its own validation logic const addressGroup = new FormGroup({ @@ -316,6 +331,7 @@ Attempting to call disable/enable would throw an error. ```typescript {avoid} import {signal, effect} from '@angular/core'; +import {SignalFormControl} from '@angular/forms/signals/compat'; export class UserProfile { readonly emailControl = new SignalFormControl(''); @@ -421,6 +437,7 @@ If you want to preserve this behavior, you can provide the `NG_STATUS_CLASSES` p ```typescript import {provideSignalFormsConfig} from '@angular/forms/signals'; import {NG_STATUS_CLASSES} from '@angular/forms/signals/compat'; +import {bootstrapApplication} from '@angular/platform-browser'; bootstrapApplication(App, { providers: [ @@ -435,6 +452,7 @@ You can also provide your own custom configuration to apply whatever classes you ```typescript import {provideSignalFormsConfig} from '@angular/forms/signals'; +import {bootstrapApplication} from '@angular/platform-browser'; bootstrapApplication(App, { providers: [ diff --git a/adev-ja/src/content/guide/forms/signals/schemas.md b/adev-ja/src/content/guide/forms/signals/schemas.md index 1a21ffee5..121258441 100644 --- a/adev-ja/src/content/guide/forms/signals/schemas.md +++ b/adev-ja/src/content/guide/forms/signals/schemas.md @@ -19,7 +19,7 @@ contactForm = form(this.contactModel, (schemaPath) => { graph TD A["form(model, schemaFn)"] --> B["Schema function runs ONCE"] B --> C["required(path)"] - B --> D["disabled(path, logicFn)"] + B --> D["disabled(path, {when: logicFn})"] B --> E["validate(path, logicFn)"] B --> F["apply(path, schema)"] C --> G["Reactive: recomputes on signal change"] @@ -55,7 +55,7 @@ TIP: If rules only appear in one place, an inline schema function works just as You can apply a reusable schema to a specific path in a form by using the `apply()` function. When you call `apply()`, the schema receives a scoped path that only sees the fields within that sub-path: ```ts -import {apply} from '@angular/forms/signals'; +import {apply, form} from '@angular/forms/signals'; profileForm = form(this.profileModel, (schemaPath) => { apply(schemaPath.name, nameSchema); diff --git a/adev-ja/src/content/guide/i18n/deploy.md b/adev-ja/src/content/guide/i18n/deploy.md index 26fe64523..440df8883 100644 --- a/adev-ja/src/content/guide/i18n/deploy.md +++ b/adev-ja/src/content/guide/i18n/deploy.md @@ -5,7 +5,8 @@ For example, your French version is located in the `myapp/fr` directory and the The HTML `base` tag with the `href` attribute specifies the base URI, or URL, for relative links. If you set the `"localize"` option in [`angular.json`][GuideWorkspaceConfig] workspace build configuration file to `true` or to an array of locale IDs, the CLI adjusts the base `href` for each version of the application. -To adjust the base `href` for each version of the application, the CLI adds the locale to the configured `"subPath"`. +To adjust the base `href` for each version of the application, the CLI uses the configured `"subPath"` as the URL segment for the locale. +If you don't set `"subPath"`, it defaults to the locale ID. Specify the `"subPath"` for each locale in your [`angular.json`][GuideWorkspaceConfig] workspace build configuration file. The following example displays `"subPath"` set to an empty string. diff --git a/adev-ja/src/content/guide/i18n/merge.md b/adev-ja/src/content/guide/i18n/merge.md index 2a819e5ce..b592eb1cd 100644 --- a/adev-ja/src/content/guide/i18n/merge.md +++ b/adev-ja/src/content/guide/i18n/merge.md @@ -57,7 +57,7 @@ HELPFUL: Ahead-of-time (AOT) compilation is required to localize component templ If you changed this setting, set `"aot"` to `true` in order to use AOT. HELPFUL: Due to the deployment complexities of i18n and the need to minimize rebuild time, the development server only supports localizing a single locale at a time. -If you set the `"localize"` option to `true`, define more than one locale, and use `ng serve`; then an error occurs. +If you set the `"localize"` option to `true` or to an array of more than one locale, and use `ng serve`, the development server logs a warning and disables localization. If you want to develop against a specific locale, set the `"localize"` option to a specific locale. For example, for French \(`fr`\), specify `"localize": ["fr"]`. diff --git a/adev-ja/src/content/guide/i18n/overview.md b/adev-ja/src/content/guide/i18n/overview.md index 5b2a7adc4..56911ef21 100644 --- a/adev-ja/src/content/guide/i18n/overview.md +++ b/adev-ja/src/content/guide/i18n/overview.md @@ -1,4 +1,5 @@ -# Angular Internationalization (i18n) + + _Internationalization_, sometimes referenced as i18n, is the process of designing and preparing your project for use in different locales around the world. _Localization_ is the process of building versions of your project for different locales. diff --git a/adev-ja/src/content/guide/ngmodules/overview.md b/adev-ja/src/content/guide/ngmodules/overview.md index a66b96124..0f849f716 100644 --- a/adev-ja/src/content/guide/ngmodules/overview.md +++ b/adev-ja/src/content/guide/ngmodules/overview.md @@ -177,6 +177,7 @@ You can use the [`bootstrapModule`](/api/core/PlatformRef#bootstrapModule) metho ```typescript import {platformBrowser} from '@angular/platform-browser'; +import {NgModule} from '@angular/core'; @NgModule({ bootstrap: [MyApplication], diff --git a/adev-ja/src/content/guide/signals/debounced.md b/adev-ja/src/content/guide/signals/debounced.md index 0d424d964..9766071b2 100644 --- a/adev-ja/src/content/guide/signals/debounced.md +++ b/adev-ja/src/content/guide/signals/debounced.md @@ -5,7 +5,7 @@ IMPORTANT: `debounced` is [experimental](reference/releases#experimental). It's Use `debounced` to delay reacting to a signal's value until it stops changing. It returns a `Resource` whose value reflects the debounced value of the source signal. ```angular-ts -import {debounced, resource, signal} from '@angular/core'; +import {Component, debounced, resource, signal} from '@angular/core'; @Component({ template: ` diff --git a/adev-ja/src/content/guide/testing/services.md b/adev-ja/src/content/guide/testing/services.md index d05bd0258..7af23d220 100644 --- a/adev-ja/src/content/guide/testing/services.md +++ b/adev-ja/src/content/guide/testing/services.md @@ -131,7 +131,7 @@ A stub controls what a dependency returns, but sometimes you also need to verify ```ts { header: 'order-total.spec.ts' } import {TestBed} from '@angular/core/testing'; -import {beforeEach, describe, expect, it, vi, type Mocked} from 'vitest'; +import {afterEach, beforeEach, describe, expect, it, vi, type Mocked} from 'vitest'; import {OrderTotal} from './order-total'; import {TaxCalculator} from './tax-calculator'; diff --git a/adev-ja/src/content/reference/errors/NG01055.md b/adev-ja/src/content/reference/errors/NG01055.md new file mode 100644 index 000000000..c24c47cb3 --- /dev/null +++ b/adev-ja/src/content/reference/errors/NG01055.md @@ -0,0 +1,39 @@ +# Missing control for `formControlName` + +A `formControlName` in the template points at a control that the `FormGroup` does not have. The directive looks the control up by its path and finds nothing. + +This can happen when the template and the form group are built from the same data but use different filters. One item is rendered in the template but was never given a control in the form group, so its `formControlName` has nothing to bind to. + +```angular-ts +@Component({ + template: ` +
+ + @for (id of visibleIds; track id) { + + } + +
+ `, +}) +class App { + // The template renders an input for both ids... + visibleIds = [1, 2]; + // ...but the form group only has a control for id 1. + form = new FormGroup({ + options: new FormGroup({ + 1: new FormControl(''), + }), + }); +} +``` + +## Debugging the error + +The error message lists the controls that do exist at that level, for example: + +``` +Cannot find control with path: 'options -> 2'. Controls that do exist at this level: 1. +``` + +Compare it with the names your template binds to. Then either add the missing control to the `FormGroup`, or stop rendering the `formControlName` for the item that has no control. diff --git a/adev-ja/src/content/reference/errors/NG01354.md b/adev-ja/src/content/reference/errors/NG01354.md index d2e12681a..927da81bc 100644 --- a/adev-ja/src/content/reference/errors/NG01354.md +++ b/adev-ja/src/content/reference/errors/NG01354.md @@ -22,6 +22,7 @@ Use the same class that the warning names — `NgForm` for template-driven forms ```angular-ts // Parent uses a template-driven form:
... import {ControlContainer, NgForm} from '@angular/forms'; +import {Component} from '@angular/core'; @Component({ selector: 'app-child', @@ -36,6 +37,7 @@ export class Child { ```angular-ts // Parent uses a reactive form:
...
import {ControlContainer, FormGroupDirective} from '@angular/forms'; +import {Component} from '@angular/core'; @Component({ selector: 'app-child', diff --git a/adev-ja/src/content/reference/errors/NG02825.md b/adev-ja/src/content/reference/errors/NG02825.md index 3c9f7a754..55e6b82bd 100644 --- a/adev-ja/src/content/reference/errors/NG02825.md +++ b/adev-ja/src/content/reference/errors/NG02825.md @@ -8,6 +8,7 @@ Set `maxResponseBodySize` in the `provideServerRendering` options to increase th ```ts import {provideServerRendering, withRoutes} from '@angular/ssr'; +import {ApplicationConfig} from '@angular/core'; import {serverRoutes} from './app.routes.server'; const serverConfig: ApplicationConfig = { diff --git a/adev-ja/src/content/reference/errors/NG05101.md b/adev-ja/src/content/reference/errors/NG05101.md index 1459c4ecb..8dc3c17a8 100644 --- a/adev-ja/src/content/reference/errors/NG05101.md +++ b/adev-ja/src/content/reference/errors/NG05101.md @@ -4,15 +4,7 @@ This error occurs when Angular tries to set up a listener for an event but none ## Debugging the error -The error message includes the unrecognized event name, which is usually enough to spot the problem. The most common cause is a typo in the event binding: - -```html - - - - - -``` +The error message includes the unrecognized event name. It occurs only when no registered plugin supports the event, for example when an `EventManager` is created with a custom plugin list that does not include `DomEventsPlugin`, as in unit tests or custom setups. If the event name is intentional and belongs to a third-party plugin (for example, a Hammer.js gesture event), make sure the plugin is provided via the `EVENT_MANAGER_PLUGINS` token: diff --git a/adev-ja/src/content/reference/errors/NG05201.md b/adev-ja/src/content/reference/errors/NG05201.md index 6c81b9292..a95821db7 100644 --- a/adev-ja/src/content/reference/errors/NG05201.md +++ b/adev-ja/src/content/reference/errors/NG05201.md @@ -1,43 +1,35 @@ -# Unsafe value used in a resource URL context +# Unsafe resource URL passed to DomSanitizer -Angular throws this error when you bind a value to an attribute that loads an external resource — like ` +```angular-ts +@Component({ + selector: 'app-root', + template: ``, +}) +export class App { + private readonly sanitizer = inject(DomSanitizer); + url = this.sanitizer.sanitize(SecurityContext.RESOURCE_URL, 'https://example.com/embed'); // throws NG05201 +} ``` -If the URL comes from a trusted, controlled source, mark it safe using `DomSanitizer.bypassSecurityTrustResourceUrl`: +If the URL comes from a trusted, controlled source, mark it safe using `DomSanitizer.bypassSecurityTrustResourceUrl` and bind the result directly: -```typescript +```angular-ts import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser'; +import {Component, inject} from '@angular/core'; @Component({ - selector: 'my-app', + selector: 'app-root', + template: ``, }) export class App { safeUrl: SafeResourceUrl = inject(DomSanitizer).bypassSecurityTrustResourceUrl( @@ -46,8 +38,6 @@ export class App { } ``` -```html - -``` +CRITICAL: `bypassSecurityTrustResourceUrl` is an escape hatch, not a sanitizer. Never pass user-supplied URLs through it — that would allow an attacker to load arbitrary content, including malicious scripts. Only use it when the value is fully under your control. -`bypassSecurityTrustResourceUrl` is an escape hatch, not a sanitizer. Never pass user-supplied URLs through it — that would allow an attacker to load arbitrary content, including malicious scripts. Only use it when the value is fully under your control. +For more information, see the [Security guide](best-practices/security#trusting-safe-values). diff --git a/adev-ja/src/content/reference/errors/NG0904.md b/adev-ja/src/content/reference/errors/NG0904.md new file mode 100644 index 000000000..7b92c4807 --- /dev/null +++ b/adev-ja/src/content/reference/errors/NG0904.md @@ -0,0 +1,66 @@ +# Unsafe value used in a resource URL context + +Angular throws this error, with the message `unsafe value used in a resource URL context`, when a template or host binding sets an attribute that loads an external resource — like ``, +}) +export class App { + url = 'https://example.com/embed'; +} +``` + +Passing the value through `DomSanitizer.sanitize()` with `SecurityContext.URL` does not help. `sanitize()` returns a plain string, and a plain string is never accepted in a resource URL context: + +```angular-ts +@Component({ + selector: 'app-root', + template: ``, +}) +export class App { + private readonly sanitizer = inject(DomSanitizer); + sanitized = this.sanitizer.sanitize(SecurityContext.URL, 'https://example.com/embed'); // still throws NG0904 +} +``` + +Calling `sanitize()` with `SecurityContext.RESOURCE_URL` instead throws too, but with [NG05201](errors/NG05201), because `sanitize()` cannot make an arbitrary string safe in a resource URL context. + +If the URL comes from a trusted, controlled source, mark it safe using `DomSanitizer.bypassSecurityTrustResourceUrl`: + +```angular-ts +import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser'; +import {Component, inject} from '@angular/core'; + +@Component({ + selector: 'app-root', + template: ``, +}) +export class App { + safeUrl: SafeResourceUrl = inject(DomSanitizer).bypassSecurityTrustResourceUrl( + 'https://example.com/embed', + ); +} +``` + +CRITICAL: `bypassSecurityTrustResourceUrl` is an escape hatch, not a sanitizer. Never pass user-supplied URLs through it — that would allow an attacker to load arbitrary content, including malicious scripts. Only use it when the value is fully under your control. + +For more information, see the [Security guide](best-practices/security#trusting-safe-values). diff --git a/adev-ja/src/content/reference/errors/NG0991.md b/adev-ja/src/content/reference/errors/NG0991.md index 92480b7f3..dee88277f 100644 --- a/adev-ja/src/content/reference/errors/NG0991.md +++ b/adev-ja/src/content/reference/errors/NG0991.md @@ -31,6 +31,7 @@ Fix it the same way as you would in an interceptor pipeline generally: let the e The most common way to accidentally cause this is catching an error and swallowing it with `EMPTY`: ```typescript +import {rxResource} from '@angular/core/rxjs-interop'; import {EMPTY, catchError} from 'rxjs'; const userResource = rxResource({ diff --git a/adev-ja/src/content/reference/extended-diagnostics/NG8103.md b/adev-ja/src/content/reference/extended-diagnostics/NG8103.md index 831190915..902cdd7d5 100644 --- a/adev-ja/src/content/reference/extended-diagnostics/NG8103.md +++ b/adev-ja/src/content/reference/extended-diagnostics/NG8103.md @@ -1,8 +1,8 @@ # Missing control flow directive This diagnostics ensures that a standalone component which uses known control flow directives -(such as `*ngIf`, `*ngFor`, or `*ngSwitch`) in a template, also imports those directives either -individually or by importing the `CommonModule`. +(such as `*ngIf`, `*ngFor`, `*ngSwitchCase`, or `*ngSwitchDefault`) in a template, also imports +those directives either individually or by importing the `CommonModule`. ```angular-ts import {Component} from '@angular/core'; diff --git a/adev-ja/src/content/reference/migrations/inject-function.md b/adev-ja/src/content/reference/migrations/inject-function.md index 04bebe6bf..4f904a074 100644 --- a/adev-ja/src/content/reference/migrations/inject-function.md +++ b/adev-ja/src/content/reference/migrations/inject-function.md @@ -17,7 +17,7 @@ import {Component, Inject, Optional} from '@angular/core'; import {MyService} from './service'; import {DI_TOKEN} from './token'; -@Component() +@Component({template: ''}) export class MyComp { constructor( private service: MyService, @@ -33,7 +33,7 @@ import {Component, inject} from '@angular/core'; import {MyService} from './service'; import {DI_TOKEN} from './token'; -@Component() +@Component({template: ''}) export class MyComp { private service = inject(MyService); readonly token = inject(DI_TOKEN, {optional: true}); @@ -70,7 +70,7 @@ additional constructor signature to keep it backwards compatible, at the expense import {Component} from '@angular/core'; import {MyService} from './service'; -@Component() +@Component({template: ''}) export class MyComp { constructor(private service: MyService) {} } @@ -82,7 +82,7 @@ export class MyComp { import {Component, inject} from '@angular/core'; import {MyService} from './service'; -@Component() +@Component({template: ''}) export class MyComp { private service = inject(MyService); @@ -102,7 +102,7 @@ fixed in `inject()` which can cause new compilation errors to show up. If you en the migration will produce a non-null assertion after the `inject()` call to match the old type, at the expense of potentially hiding type errors. -**NOTE:** non-null assertions won't be added to parameters that are already typed to be nullable, +NOTE: non-null assertions won't be added to parameters that are already typed to be nullable, because the code that depends on them likely already accounts for their nullability. #### Before {#non-nullable-optional-before} @@ -111,7 +111,7 @@ because the code that depends on them likely already accounts for their nullabil import {Component, Inject, Optional} from '@angular/core'; import {TOKEN_ONE, TOKEN_TWO} from './token'; -@Component() +@Component({template: ''}) export class MyComp { constructor( @Inject(TOKEN_ONE) @Optional() private tokenOne: number, @@ -126,7 +126,7 @@ export class MyComp { import {Component, inject} from '@angular/core'; import {TOKEN_ONE, TOKEN_TWO} from './token'; -@Component() +@Component({template: ''}) export class MyComp { // Note the `!` at the end. private tokenOne = inject(TOKEN_ONE, {optional: true})!; diff --git a/adev-ja/src/content/reference/migrations/route-lazy-loading.md b/adev-ja/src/content/reference/migrations/route-lazy-loading.md index 16901a528..cd6f239e2 100644 --- a/adev-ja/src/content/reference/migrations/route-lazy-loading.md +++ b/adev-ja/src/content/reference/migrations/route-lazy-loading.md @@ -32,6 +32,8 @@ The migration will check all the components in the routes, check if they are sta #### Before ```typescript {header: "app.module.ts"} +import {NgModule} from '@angular/core'; +import {RouterModule} from '@angular/router'; import {Home} from './home'; @NgModule({ diff --git a/adev-ja/src/content/reference/migrations/router-testing-module-migration.md b/adev-ja/src/content/reference/migrations/router-testing-module-migration.md index 6870b45c9..96e35e1c0 100644 --- a/adev-ja/src/content/reference/migrations/router-testing-module-migration.md +++ b/adev-ja/src/content/reference/migrations/router-testing-module-migration.md @@ -2,7 +2,7 @@ This schematic migrates usages of `RouterTestingModule` inside tests to `RouterModule`. -When a test imports `SpyLocation` from `@angular/common/testing` and uses `urlChanges` property , the schematic will also add `provideLocationMocks()` to preserve the original behavior. +When a test imports `SpyLocation` from `@angular/common/testing` and uses the `urlChanges` property, the schematic will also add `provideLocationMocks()` to preserve the original behavior. Run the schematic with: @@ -24,6 +24,7 @@ Before: ```ts import {RouterTestingModule} from '@angular/router/testing'; +import {TestBed} from '@angular/core/testing'; import {SpyLocation} from '@angular/common/testing'; describe('test', () => { @@ -39,6 +40,7 @@ After: ```ts import {RouterModule} from '@angular/router'; +import {TestBed} from '@angular/core/testing'; import {SpyLocation} from '@angular/common/testing'; describe('test', () => { @@ -56,6 +58,7 @@ Before: ```ts import {RouterTestingModule} from '@angular/router/testing'; +import {TestBed} from '@angular/core/testing'; import {SpyLocation} from '@angular/common/testing'; describe('test', () => { @@ -78,6 +81,7 @@ After: ```ts import {RouterModule} from '@angular/router'; +import {TestBed} from '@angular/core/testing'; import {SpyLocation, provideLocationMocks} from '@angular/common/testing'; describe('test', () => { diff --git a/adev-ja/src/content/reference/migrations/signal-queries.md b/adev-ja/src/content/reference/migrations/signal-queries.md index 0081e37c0..fdad1a9e4 100644 --- a/adev-ja/src/content/reference/migrations/signal-queries.md +++ b/adev-ja/src/content/reference/migrations/signal-queries.md @@ -27,10 +27,10 @@ See more details in the section [below](#vscode-extension). **Before** ```angular-ts -import {Component, ContentChild} from '@angular/core'; +import {Component, ContentChild, ElementRef} from '@angular/core'; @Component({ - template: `Has ref: {{ someRef ? 'Yes' : 'No' }}`, + template: `Has ref: {{ ref ? 'Yes' : 'No' }}`, }) export class MyComponent { @ContentChild('someRef') ref: ElementRef | undefined = undefined; @@ -46,10 +46,10 @@ export class MyComponent { **After** ```angular-ts -import {Component, contentChild} from '@angular/core'; +import {Component, contentChild, ElementRef} from '@angular/core'; @Component({ - template: `Has ref: {{ someRef() ? 'Yes' : 'No' }}`, + template: `Has ref: {{ ref() ? 'Yes' : 'No' }}`, }) export class MyComponent { readonly ref = contentChild('someRef'); diff --git a/adev-ja/src/content/reference/migrations/standalone.md b/adev-ja/src/content/reference/migrations/standalone.md index b6c816a2d..0db21adbc 100644 --- a/adev-ja/src/content/reference/migrations/standalone.md +++ b/adev-ja/src/content/reference/migrations/standalone.md @@ -191,7 +191,7 @@ export class App {} ```typescript {header: "./main.ts"} import {bootstrapApplication} from '@angular/platform-browser'; -import {App} from './app'; +import {App} from './app/app'; bootstrapApplication(App).catch((e) => console.error(e)); ``` diff --git a/adev-ja/src/content/reference/press-kit.md b/adev-ja/src/content/reference/press-kit.md index 457cc5f86..46d4973bd 100644 --- a/adev-ja/src/content/reference/press-kit.md +++ b/adev-ja/src/content/reference/press-kit.md @@ -52,9 +52,9 @@ In this example, we’ve adapted the shield to create an Angular Signals logo va If you are creating your own logo, we encourage differentiating your logo from ours to not cause confusion. When adapting the logo, you are free to change and adapt the colors and shape to make it your own. - + When representing Angular with the original logo, please follow these guidelines: -![Rhubarb the small cat](assets/images/press-kit/do_and_dont.png "Rhubarb the small cat") +![Rhubarb the small cat](assets/images/press-kit/do_and_dont.png 'Rhubarb the small cat') @@ -63,7 +63,7 @@ When representing Angular with the original logo, please follow these guidelines ### 2016 Angular (v3-v16) -In 2023 , we announced a modernized logo with v17. We advise against using the former Angular logo to prevent confusion. You can view the old press kit on our [old docs site](https://angular.io/presskit). +In 2023, we announced a modernized logo with v17. We advise against using the former Angular logo to prevent confusion. You can view the old press kit on our [old docs site](https://angular.io/presskit). ### AngularJS diff --git a/adev-ja/src/content/tools/cli/aot-metadata-errors.md b/adev-ja/src/content/tools/cli/aot-metadata-errors.md index 9de616796..fbe9007f8 100644 --- a/adev-ja/src/content/tools/cli/aot-metadata-errors.md +++ b/adev-ja/src/content/tools/cli/aot-metadata-errors.md @@ -125,6 +125,7 @@ export class MyComponent {} You'd also get this error if you imported `someTemplate` from some other module and neglected to initialize it there. ```ts +import {Component} from '@angular/core'; // ERROR - not initialized there either import {someTemplate} from './config'; @@ -256,7 +257,7 @@ Here's an illustrative example. ```ts // CORRECTED -import { Inject } from '@angular/core'; +import {Inject, InjectionToken, Component} from '@angular/core'; export const WINDOW = new InjectionToken('Window'); export function _window() { return window; } @@ -278,7 +279,7 @@ uses the `@Inject(WINDOW)` to generate the injection code. Angular does something similar with the `DOCUMENT` token so you can inject the browser's `document` object \(or an abstraction of it, depending upon the platform in which the application runs\). ```ts -import { Inject } from '@angular/core'; +import {Inject, Component} from '@angular/core'; import { DOCUMENT } from '@angular/common'; @Component({ … }) diff --git a/adev-ja/src/content/tools/cli/end-to-end.md b/adev-ja/src/content/tools/cli/end-to-end.md index d2385dba7..da915ba7d 100644 --- a/adev-ja/src/content/tools/cli/end-to-end.md +++ b/adev-ja/src/content/tools/cli/end-to-end.md @@ -20,18 +20,18 @@ Cannot find "e2e" target for the specified project. You can add a package that implements these capabilities. For example: +Playwright: ng add playwright-ng-schematics Cypress: ng add @cypress/schematic Nightwatch: ng add @nightwatch/schematics WebdriverIO: ng add @wdio/schematics -Playwright: ng add playwright-ng-schematics Puppeteer: ng add @puppeteer/ng-schematics Would you like to add a package with "e2e" capabilities now? -No -❯ Cypress +❯ No +Playwright +Cypress Nightwatch WebdriverIO -Playwright Puppeteer ``` diff --git a/adev-ja/src/content/tools/cli/template-typecheck.md b/adev-ja/src/content/tools/cli/template-typecheck.md index e394ced02..760d47033 100644 --- a/adev-ja/src/content/tools/cli/template-typecheck.md +++ b/adev-ja/src/content/tools/cli/template-typecheck.md @@ -3,7 +3,8 @@ ## Overview of template type checking Just as TypeScript catches type errors in your code, Angular checks the expressions and bindings within the templates of your application and can report any type errors it finds. -Angular has two modes of doing this, depending on the value of the `strictTemplates` flag in [Angular's compiler options](reference/configs/angular-compiler-options). +By default, Angular checks templates in strict mode. +If needed, you can temporarily opt out by setting the `strictTemplates` flag in [Angular's compiler options](reference/configs/angular-compiler-options) to `false`, which falls back to a less thorough basic mode. ### Basic mode @@ -19,7 +20,7 @@ The compiler does not verify that the value of `user.address.city` is assignable The compiler also has some major limitations in this mode: -- Importantly, it doesn't check embedded views, such as `*ngIf`, `*ngFor`, other `` embedded view. +- Importantly, it doesn't check embedded views, such as `@if`, `@for`, `*ngIf`, `*ngFor`, or other `` embedded views. - It doesn't figure out the types of `#refs`, the results of pipes, or the type of `$event` in event bindings. In many cases, these things end up as type `any`, which can cause subsequent parts of the expression to go unchecked. @@ -30,7 +31,7 @@ Strict mode is the default, and is enabled when the `strictTemplates` flag is `t In strict mode, Angular does the following: -- Checks embedded views \(such as those within an `*ngIf` or `*ngFor`\) +- Checks embedded views \(such as those within `@if`, `@for`, `*ngIf`, or `*ngFor`\) - Infers the correct return type of pipes - Infers the correct type of local references to directives and pipes - Verifies that component/directive bindings are assignable to their `input()`s @@ -89,17 +90,20 @@ In case of a false positive like these, there are a few options: Each following option is set to the value for `strictTemplates` \(`true` when `strictTemplates` is `true` and conversely, the other way around\): -| Strictness flag | Effect | -| :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `strictInputTypes` | Whether the assignability of a binding expression to the `@Input()` field is checked. Also affects the inference of directive generic types. | -| `strictNullInputTypes` | Whether `strictNullChecks` is honored when checking `@Input()` bindings \(per `strictInputTypes`\). Turning this off can be useful when using a library that was not built with `strictNullChecks` in mind. | -| `strictAttributeTypes` | Whether to check `@Input()` bindings that are made using text attributes. For example, `` \(setting the `disabled` property to the string `'true'`\) vs `` \(setting the `disabled` property to the boolean `true`\). | -| `strictSafeNavigationTypes` | Whether the return type of safe navigation operations \(for example, `user?.name` will be correctly inferred based on the type of `user`\). If disabled, `user?.name` will be of type `any`. | -| `strictDomLocalRefTypes` | Whether local references to DOM elements will have the correct type. If disabled `ref` will be of type `any` for ``. | -| `strictOutputEventTypes` | Whether `$event` will have the correct type for event bindings to component/directive an `@Output()`, or to animation events. If disabled, it will be `any`. | -| `strictDomEventTypes` | Whether `$event` will have the correct type for event bindings to DOM events. If disabled, it will be `any`. | -| `strictContextGenerics` | Whether the type parameters of generic components will be inferred correctly \(including any generic bounds\). If disabled, any type parameters will be `any`. | -| `strictLiteralTypes` | Whether object and array literals declared in the template will have their type inferred. If disabled, the type of such literals will be `any`. | +| Strictness flag | Effect | +| :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `strictInputTypes` | Whether the assignability of a binding expression to the `@Input()` field is checked. Also affects the inference of directive generic types. | +| `strictNullInputTypes` | Whether `strictNullChecks` is honored when checking `@Input()` bindings \(per `strictInputTypes`\). Turning this off can be useful when using a library that was not built with `strictNullChecks` in mind. | +| `strictAttributeTypes` | Whether to check `@Input()` bindings that are made using text attributes. For example, `` \(setting the `disabled` property to the string `'true'`\) vs `` \(setting the `disabled` property to the boolean `true`\). | +| `strictSafeNavigationTypes` | Whether the return type of safe navigation operations \(for example, `user?.name` will be correctly inferred based on the type of `user`\). If disabled, `user?.name` will be of type `any`. | +| `strictDomLocalRefTypes` | Whether local references to DOM elements will have the correct type. If disabled `ref` will be of type `any` for ``. | +| `strictOutputEventTypes` | Whether `$event` will have the correct type for event bindings to component/directive an `@Output()`, or to animation events. If disabled, it will be `any`. | +| `strictDomEventTypes` | Whether `$event` will have the correct type for event bindings to DOM events. If disabled, it will be `any`. | +| `strictContextGenerics` | Whether the type parameters of generic components will be inferred correctly \(including any generic bounds\). If disabled, any type parameters will be `any`. | +| `strictLiteralTypes` | Whether object and array literals declared in the template will have their type inferred. If disabled, the type of such literals will be `any`. | + +Other options (independent of `strictTemplates`): + | Strictness flag | Effect | | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `strictInputAccessModifiers` | Whether access modifiers such as `private`/`protected`/`readonly` are honored when assigning a binding expression to an `@Input()` or `input()`. If disabled, the access modifiers of the input are ignored; only the type is checked. Note: This checking only applies to inputs, not outputs. | diff --git a/adev-ja/src/content/tools/devtools/signals.md b/adev-ja/src/content/tools/devtools/signals.md new file mode 100644 index 000000000..cdc1a53ff --- /dev/null +++ b/adev-ja/src/content/tools/devtools/signals.md @@ -0,0 +1,60 @@ +# Debug the signal graph + +The **Components** tab can display a live signal graph for the selected component or directive. +The graph shows how the signals, computeds, effects, and other reactive nodes in that component depend on each other, which makes it easier to understand and debug reactive data flows. + +A screenshot of the 'Components' tab in Angular DevTools with the signal graph open for the selected component. The graph shows a signal node connected by edges to three computed nodes which the component's template reads, and the properties of the component are listed in a sidebar to the right. + +## Open the signal graph + +In the **Components** tab, select a component or directive. +In the property pane header, click **Show Signal Graph**. +The signal graph opens next to the component tree. + +You can also jump directly to a specific signal: in the properties view, click the signal graph button next to the property, or, when the property has more than one action, open its actions menu and choose **Show in signal graph**. + +The **Show Signal Graph** button appears automatically when the inspected application supports the signal graph API — there is no setting to enable. + +## Read the graph + +Nodes in the graph represent the reactive nodes that participate in the selected component: + +- **Signal**: a writable signal created with `signal()`. +- **Computed**: a derived value created with `computed()`. +- **Effect**: a side effect created with `effect()` or `afterRenderEffect()`. +- **Linked signal**: a writable signal created with `linkedSignal()`. +- **Template**: the component's template, which reads signals when it renders. +- **Resource**: signals that belong to a `resource()` or `rxResource()` are grouped into a collapsible cluster. Click **Expand** on the cluster node to see the individual nodes inside. + +Lines between nodes show reactive dependencies: each node is connected to the producers it reads from (upstream) and the dependants that read it (downstream). + +TIP: Nodes are labeled with the `debugName` of their reactive node. Giving your signals, computeds, and especially effects — which are usually not assigned to a class member — a `debugName` makes the graph much easier to read: + +```ts +effect( + () => { + // ... + }, + {debugName: 'name in graph'}, +); +``` + +## Search the graph + +Click the search icon in the graph toolbar to find a node by name. +Angular DevTools highlights every match and lets you step through them. + +## Inspect a node + +Click a node to open its details panel, which shows the node's current value as an expandable tree and offers the following actions: + +- **View source**: navigate to the node's definition in the Sources tab (Chrome) or Debugger tab (Firefox). The action is disabled when the source location is not available. +- **Set breakpoint**: pause execution when the signal updates. Breakpoints are only available in Chromium-based browsers. +- **Watch signal**: log the signal's updates and invalidations to the browser console. Click the action again to stop watching. +- **Highlight upstream / downstream**: trace the paths of the node's dependencies or its dependants. + +If the component uses no signals, Angular DevTools shows a "No signals in this component" message instead of the graph. + +## Version support + +The signal graph requires an application built with Angular 19 or later running in development mode. diff --git a/adev-ja/src/content/tools/language-service.md b/adev-ja/src/content/tools/language-service.md index 58d9aba8e..d6492cb35 100644 --- a/adev-ja/src/content/tools/language-service.md +++ b/adev-ja/src/content/tools/language-service.md @@ -58,6 +58,8 @@ You can then click "Go to definition" or press F12 to go directly to the definit Angular Language Service is currently available as an extension for [Visual Studio Code](https://code.visualstudio.com), [WebStorm](https://www.jetbrains.com/webstorm), [Sublime Text](https://www.sublimetext.com), [Zed](https://zed.dev), [Neovim](https://neovim.io), and [Eclipse IDE](https://www.eclipse.org/eclipseide). +NOTE: The Angular team maintains and distributes the Visual Studio Code extension, which is supported in Visual Studio Code and VS Code-based editors such as Cursor and Antigravity. Integrations for other editors are maintained by their respective editor vendors or by the community. Report issues specific to those integrations to their maintainers. + ### Visual Studio Code In [Visual Studio Code](https://code.visualstudio.com), install the extension from the [Extensions: Marketplace](https://marketplace.visualstudio.com/items?itemName=Angular.ng-template). @@ -170,7 +172,7 @@ Angular Language Service can be used with Neovim by using the [nvim-lspconfig](h ### Zed -In [Zed](https://zed.dev), install the extension from [Extensions: Marketplace](https://zed.dev/extensions/angular). +In [Zed](https://zed.dev), install the community-maintained extension from [Extensions: Marketplace](https://zed.dev/extensions/angular). ## How the Language Service works diff --git a/adev-ja/src/content/tutorials/first-app/steps/09-services/README.md b/adev-ja/src/content/tutorials/first-app/steps/09-services/README.md index 46b3fdf0d..c70729cc0 100644 --- a/adev-ja/src/content/tutorials/first-app/steps/09-services/README.md +++ b/adev-ja/src/content/tutorials/first-app/steps/09-services/README.md @@ -51,9 +51,9 @@ For now, your app's new service uses the data that has, so far, been created loc In the **Edit** pane of your IDE: -1. In `src/app/home/home.ts`, from `Home`, copy the `housingLocationList` variable and its array value. +1. In `src/app/home/home.ts`, from `Home`, copy the `baseUrl` variable and the `housingLocationList` variable with its array value. 1. In `src/app/housing.service.ts`: - 1. Inside the `HousingService` class, paste the variable that you copied from `Home` in the previous step. + 1. Inside the `HousingService` class, paste the variables that you copied from `Home` in the previous step. 1. Inside the `HousingService` class, paste these functions after the data you just copied. These functions allow dependencies to access the service's data. @@ -83,7 +83,7 @@ In the **Edit** pane of your IDE, in `src/app/home/home.ts`: -1. From `Home`, delete the `housingLocationList` array entries and assign `housingLocationList` the value of empty array (`[]`). In a few steps you will update the code to pull the data from the `HousingService`. +1. From `Home`, delete the `baseUrl` variable, delete the `housingLocationList` array entries and assign `housingLocationList` the value of empty array (`[]`). In a few steps you will update the code to pull the data from the `HousingService`. 1. In `Home`, add the following code to inject the new service and initialize the data for the app. The `constructor` is the first function that runs when this component is created. The code in the `constructor` will assign the `housingLocationList` the value returned from the call to `getAllHousingLocations`. diff --git a/adev-ja/src/content/tutorials/first-app/steps/11-details-page/README.md b/adev-ja/src/content/tutorials/first-app/steps/11-details-page/README.md index f16d29add..0893e571b 100644 --- a/adev-ja/src/content/tutorials/first-app/steps/11-details-page/README.md +++ b/adev-ja/src/content/tutorials/first-app/steps/11-details-page/README.md @@ -33,7 +33,7 @@ In this case, `:id` is dynamic and will change based on how the route is request The `routerLink` directive enables Angular's router to create dynamic links in the application. The value assigned to the `routerLink` is an array with two entries: the static portion of the path and the dynamic data. - For the `routerLink` to work in the template, add a file level import of `RouterLink` and `RouterOutlet` from '@angular/router', then update the component `imports` array to include both `RouterLink` and `RouterOutlet`. + For the `routerLink` to work in the template, add a file level import of `RouterLink` from '@angular/router', then update the component `imports` array to include `RouterLink`. 1. At this point you can confirm that the routing is working in your app. In the browser, refresh the home page and click the "Learn More" button for a housing location. diff --git a/adev-ja/src/content/tutorials/first-app/steps/14-http/README.md b/adev-ja/src/content/tutorials/first-app/steps/14-http/README.md index 6fa5a6b7b..bf8a24598 100644 --- a/adev-ja/src/content/tutorials/first-app/steps/14-http/README.md +++ b/adev-ja/src/content/tutorials/first-app/steps/14-http/README.md @@ -176,7 +176,7 @@ The data source has been configured, the next step is to update your web app to 1. Once all the updates are complete, your updated service should match the following code. - + From 4c1942a1a1e72aeacc512ea3968be12b62acb0bf Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 25 Oct 2026 10:17:12 +0900 Subject: [PATCH 03/51] fix(app): apply upstream changes to translated strings in src/app/features/home --- adev-ja/src/app/features/home/home.component.en.html | 2 +- adev-ja/src/app/features/home/home.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adev-ja/src/app/features/home/home.component.en.html b/adev-ja/src/app/features/home/home.component.en.html index 4e55dce26..5d6445209 100644 --- a/adev-ja/src/app/features/home/home.component.en.html +++ b/adev-ja/src/app/features/home/home.component.en.html @@ -13,7 +13,7 @@

Angular v22 is here!

- @if (!isUwu) { + @if (!uwu()) {

Productivity
Meets scalability

The framework for building scalable web apps with confidence

diff --git a/adev-ja/src/app/features/home/home.component.html b/adev-ja/src/app/features/home/home.component.html index 94b2a79f6..dd55dce33 100644 --- a/adev-ja/src/app/features/home/home.component.html +++ b/adev-ja/src/app/features/home/home.component.html @@ -13,7 +13,7 @@

Angular v22がリリースされました!

- @if (!isUwu) { + @if (!uwu()) {

Productivity
Meets scalability

スケーラブルなWebアプリを確信を持って構築できるフレームワーク

From 6482826f9dddf31960dc96c6af04e6e78d7d8aad Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 25 Oct 2026 10:17:13 +0900 Subject: [PATCH 04/51] fix(app): mirror upstream code changes in src/app/features/update --- .../features/update/update.component.en.ts | 73 ++++++++++++------- .../app/features/update/update.component.ts | 73 ++++++++++++------- 2 files changed, 90 insertions(+), 56 deletions(-) diff --git a/adev-ja/src/app/features/update/update.component.en.ts b/adev-ja/src/app/features/update/update.component.en.ts index f0814b62d..793ed71ce 100644 --- a/adev-ja/src/app/features/update/update.component.en.ts +++ b/adev-ja/src/app/features/update/update.component.en.ts @@ -8,7 +8,7 @@ import {Clipboard} from '@angular/cdk/clipboard'; import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu'; -import {Component, inject, signal} from '@angular/core'; +import {afterNextRender, Component, inject, input, linkedSignal, signal} from '@angular/core'; import {IconComponent} from '@angular/docs'; import {MatButtonToggle, MatButtonToggleGroup} from '@angular/material/button-toggle'; import {MatCheckbox} from '@angular/material/checkbox'; @@ -39,6 +39,10 @@ interface Option { const isWindows = typeof window !== 'undefined' && window.navigator.userAgent.includes('Windows'); +/** Versions selected when the URL doesn't specify any. */ +const DEFAULT_FROM_VERSION = '21.0'; +const DEFAULT_TO_VERSION = '22.0'; + @Component({ selector: 'adev-update-guide', templateUrl: './update.component.html', @@ -61,7 +65,8 @@ export default class UpdateComponent { protected title = signal(''); - protected level = 1; + /** The application complexity level, initialized from the `l` query param. */ + protected readonly level = linkedSignal(() => parseInt(this.levelParam() ?? '', 10) || 1); protected options: Record = { ngUpgrade: false, material: false, @@ -120,8 +125,17 @@ export default class UpdateComponent { {name: '2.1', number: 201}, {name: '2.0', number: 200}, ]; - protected from = this.versions.find((version) => version.name === '21.0')!; - protected to = this.versions.find((version) => version.name === '22.0')!; + /** The versions to update from and to, initialized from the `v` query param. */ + protected readonly from = linkedSignal( + () => + this.findVersion(this.versionsParam()?.split('-')[0]) ?? + this.findVersion(DEFAULT_FROM_VERSION)!, + ); + protected readonly to = linkedSignal( + () => + this.findVersion(this.versionsParam()?.split('-')[1]) ?? + this.findVersion(DEFAULT_TO_VERSION)!, + ); protected futureVersion = 2300; protected readonly steps: Step[] = RECOMMENDATIONS; @@ -130,19 +144,22 @@ export default class UpdateComponent { private readonly router = inject(Router); private readonly activatedRoute = inject(ActivatedRoute); + /** Bound from the `v` query param, in the `-` format (e.g. `21.0-22.0`). */ + readonly versionsParam = input(undefined, {alias: 'v'}); + /** Bound from the `l` query param, the application complexity level. */ + readonly levelParam = input(undefined, {alias: 'l'}); + constructor() { - const queryMap = this.activatedRoute.snapshot.queryParamMap; - // Detect settings in URL - this.level = parseInt(queryMap.get('l')!, 10) || this.level; - - // Detect versions of from and to - const versions = queryMap.get('v'); - if (versions) { - const [from, to] = versions.split('-'); - this.from = this.versions.find((version) => version.name === from)!; - this.to = this.versions.find((version) => version.name === to)!; - this.showUpdatePath(); - } + // Show the update path right away when the versions are provided in the URL. + afterNextRender(() => { + if (this.versionsParam()) { + this.showUpdatePath(); + } + }); + } + + private findVersion(name: string | undefined) { + return this.versions.find((version) => version.name === name); } copyCode(event: Event) { @@ -172,7 +189,7 @@ export default class UpdateComponent { this.afterRecommendations = []; // Refuse to generate recommendations for downgrades - if (this.to.number < this.from.number) { + if (this.to().number < this.from().number) { alert('We do not support downgrading versions of Angular.'); return; } @@ -182,13 +199,13 @@ export default class UpdateComponent { const labelMedium = 'medium applications'; const labelAdvanced = 'advanced applications'; - this.title.set(`${labelTitle} v${this.from.name} -> v${this.to.name} + this.title.set(`${labelTitle} v${this.from().name} -> v${this.to().name} for - ${this.level < 2 ? labelBasic : this.level < 3 ? labelMedium : labelAdvanced}`); + ${this.level() < 2 ? labelBasic : this.level() < 3 ? labelMedium : labelAdvanced}`); // Find applicable steps and organize them into before, during, and after upgrade for (const step of this.steps) { - if (step.level <= this.level && step.necessaryAsOf > this.from.number) { + if (step.level <= this.level() && step.necessaryAsOf > this.from().number) { // Check Options // Only show steps that don't have a required option // Or when the user has a matching option selected @@ -213,12 +230,12 @@ export default class UpdateComponent { step.renderedStep = await marked(this.replaceVariables(step.action)); // If you could do it before now, but didn't have to finish it before now - if (step.possibleIn <= this.from.number && step.necessaryAsOf >= this.from.number) { + if (step.possibleIn <= this.from().number && step.necessaryAsOf >= this.from().number) { this.beforeRecommendations.push(step); // If you couldn't do it before now, and you must do it now - } else if (step.possibleIn > this.from.number && step.necessaryAsOf <= this.to.number) { + } else if (step.possibleIn > this.from().number && step.necessaryAsOf <= this.to().number) { this.duringRecommendations.push(step); - } else if (step.possibleIn <= this.to.number) { + } else if (step.possibleIn <= this.to().number) { this.afterRecommendations.push(step); } } @@ -227,7 +244,7 @@ export default class UpdateComponent { // Update the URL so users can link to this transition this.router.navigate([], { relativeTo: this.activatedRoute, - queryParams: {v: `${this.from.name}-${this.to.name}`, l: this.level}, + queryParams: {v: `${this.from().name}-${this.to().name}`, l: this.level()}, queryParamsHandling: 'merge', }); @@ -257,8 +274,8 @@ export default class UpdateComponent { private async renderPreV6Instructions(): Promise { let upgradeStep: Step; - const additionalDeps = this.getAdditionalDependencies(this.to.number); - const angularVersion = this.getAngularVersion(this.to.number); + const additionalDeps = this.getAdditionalDependencies(this.to().number); + const angularVersion = this.getAngularVersion(this.to().number); const angularPackages = [ 'animations', 'common', @@ -274,7 +291,7 @@ export default class UpdateComponent { ]; // Provide npm/yarn instructions for versions before 6 - if (this.to.number < 600) { + if (this.to().number < 600) { const actionMessage = `Update all of your dependencies to the latest Angular and the right version of TypeScript.`; if (isWindows) { @@ -305,7 +322,7 @@ export default class UpdateComponent { // Npm installs typescript wrong in v5, let's manually specify // https://github.com/npm/npm/issues/16813 - if (this.packageManager === 'npm install' && this.to.number === 500) { + if (this.packageManager === 'npm install' && this.to().number === 500) { upgradeStep.action += ` \`npm install typescript@2.4.2 --save-exact\``; diff --git a/adev-ja/src/app/features/update/update.component.ts b/adev-ja/src/app/features/update/update.component.ts index 2ec67d7f6..9589cee4e 100644 --- a/adev-ja/src/app/features/update/update.component.ts +++ b/adev-ja/src/app/features/update/update.component.ts @@ -8,7 +8,7 @@ import {Clipboard} from '@angular/cdk/clipboard'; import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu'; -import {Component, inject, signal} from '@angular/core'; +import {afterNextRender, Component, inject, input, linkedSignal, signal} from '@angular/core'; import {IconComponent} from '@angular/docs'; import {MatButtonToggle, MatButtonToggleGroup} from '@angular/material/button-toggle'; import {MatCheckbox} from '@angular/material/checkbox'; @@ -39,6 +39,10 @@ interface Option { const isWindows = typeof window !== 'undefined' && window.navigator.userAgent.includes('Windows'); +/** Versions selected when the URL doesn't specify any. */ +const DEFAULT_FROM_VERSION = '21.0'; +const DEFAULT_TO_VERSION = '22.0'; + @Component({ selector: 'adev-update-guide', templateUrl: './update.component.html', @@ -61,7 +65,8 @@ export default class UpdateComponent { protected title = signal(''); - protected level = 1; + /** The application complexity level, initialized from the `l` query param. */ + protected readonly level = linkedSignal(() => parseInt(this.levelParam() ?? '', 10) || 1); protected options: Record = { ngUpgrade: false, material: false, @@ -120,8 +125,17 @@ export default class UpdateComponent { {name: '2.1', number: 201}, {name: '2.0', number: 200}, ]; - protected from = this.versions.find((version) => version.name === '21.0')!; - protected to = this.versions.find((version) => version.name === '22.0')!; + /** The versions to update from and to, initialized from the `v` query param. */ + protected readonly from = linkedSignal( + () => + this.findVersion(this.versionsParam()?.split('-')[0]) ?? + this.findVersion(DEFAULT_FROM_VERSION)!, + ); + protected readonly to = linkedSignal( + () => + this.findVersion(this.versionsParam()?.split('-')[1]) ?? + this.findVersion(DEFAULT_TO_VERSION)!, + ); protected futureVersion = 2300; protected readonly steps: Step[] = RECOMMENDATIONS; @@ -130,19 +144,22 @@ export default class UpdateComponent { private readonly router = inject(Router); private readonly activatedRoute = inject(ActivatedRoute); + /** Bound from the `v` query param, in the `-` format (e.g. `21.0-22.0`). */ + readonly versionsParam = input(undefined, {alias: 'v'}); + /** Bound from the `l` query param, the application complexity level. */ + readonly levelParam = input(undefined, {alias: 'l'}); + constructor() { - const queryMap = this.activatedRoute.snapshot.queryParamMap; - // Detect settings in URL - this.level = parseInt(queryMap.get('l')!, 10) || this.level; - - // Detect versions of from and to - const versions = queryMap.get('v'); - if (versions) { - const [from, to] = versions.split('-'); - this.from = this.versions.find((version) => version.name === from)!; - this.to = this.versions.find((version) => version.name === to)!; - this.showUpdatePath(); - } + // Show the update path right away when the versions are provided in the URL. + afterNextRender(() => { + if (this.versionsParam()) { + this.showUpdatePath(); + } + }); + } + + private findVersion(name: string | undefined) { + return this.versions.find((version) => version.name === name); } copyCode(event: Event) { @@ -172,7 +189,7 @@ export default class UpdateComponent { this.afterRecommendations = []; // Refuse to generate recommendations for downgrades - if (this.to.number < this.from.number) { + if (this.to().number < this.from().number) { alert('We do not support downgrading versions of Angular.'); return; } @@ -182,13 +199,13 @@ export default class UpdateComponent { const labelMedium = '中級'; const labelAdvanced = '上級'; - this.title.set(`${labelTitle} v${this.from.name} -> v${this.to.name} + this.title.set(`${labelTitle} v${this.from().name} -> v${this.to().name} - (${this.level < 2 ? labelBasic : this.level < 3 ? labelMedium : labelAdvanced})`); + (${this.level() < 2 ? labelBasic : this.level() < 3 ? labelMedium : labelAdvanced})`); // Find applicable steps and organize them into before, during, and after upgrade for (const step of this.steps) { - if (step.level <= this.level && step.necessaryAsOf > this.from.number) { + if (step.level <= this.level() && step.necessaryAsOf > this.from().number) { // Check Options // Only show steps that don't have a required option // Or when the user has a matching option selected @@ -213,12 +230,12 @@ export default class UpdateComponent { step.renderedStep = await marked(this.replaceVariables(step.action)); // If you could do it before now, but didn't have to finish it before now - if (step.possibleIn <= this.from.number && step.necessaryAsOf >= this.from.number) { + if (step.possibleIn <= this.from().number && step.necessaryAsOf >= this.from().number) { this.beforeRecommendations.push(step); // If you couldn't do it before now, and you must do it now - } else if (step.possibleIn > this.from.number && step.necessaryAsOf <= this.to.number) { + } else if (step.possibleIn > this.from().number && step.necessaryAsOf <= this.to().number) { this.duringRecommendations.push(step); - } else if (step.possibleIn <= this.to.number) { + } else if (step.possibleIn <= this.to().number) { this.afterRecommendations.push(step); } } @@ -227,7 +244,7 @@ export default class UpdateComponent { // Update the URL so users can link to this transition this.router.navigate([], { relativeTo: this.activatedRoute, - queryParams: {v: `${this.from.name}-${this.to.name}`, l: this.level}, + queryParams: {v: `${this.from().name}-${this.to().name}`, l: this.level()}, queryParamsHandling: 'merge', }); @@ -257,8 +274,8 @@ export default class UpdateComponent { private async renderPreV6Instructions(): Promise { let upgradeStep: Step; - const additionalDeps = this.getAdditionalDependencies(this.to.number); - const angularVersion = this.getAngularVersion(this.to.number); + const additionalDeps = this.getAdditionalDependencies(this.to().number); + const angularVersion = this.getAngularVersion(this.to().number); const angularPackages = [ 'animations', 'common', @@ -274,7 +291,7 @@ export default class UpdateComponent { ]; // Provide npm/yarn instructions for versions before 6 - if (this.to.number < 600) { + if (this.to().number < 600) { const actionMessage = `依存関係をすべて最新のAngularと適切なバージョンのTypeScriptにあわせてアップデートします。`; if (isWindows) { @@ -305,7 +322,7 @@ export default class UpdateComponent { // Npm installs typescript wrong in v5, let's manually specify // https://github.com/npm/npm/issues/16813 - if (this.packageManager === 'npm install' && this.to.number === 500) { + if (this.packageManager === 'npm install' && this.to().number === 500) { upgradeStep.action += ` \`npm install typescript@2.4.2 --save-exact\``; From afbafa9d4a97836e537cae70c16680ffe62607a0 Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 25 Oct 2026 10:17:14 +0900 Subject: [PATCH 05/51] fix(app): apply upstream changes to translated strings in src/app/features/update --- .../features/update/update.component.en.html | 35 +++++++++---------- .../app/features/update/update.component.html | 35 +++++++++---------- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/adev-ja/src/app/features/update/update.component.en.html b/adev-ja/src/app/features/update/update.component.en.html index 163a6ed63..a9afd70f1 100644 --- a/adev-ja/src/app/features/update/update.component.en.html +++ b/adev-ja/src/app/features/update/update.component.en.html @@ -11,7 +11,7 @@

Angular versions

From v. @@ -19,7 +19,7 @@

Angular versions

    @for (version of versions; track $index) {
  • -
  • @@ -32,7 +32,7 @@

    Angular versions

    To v. @@ -40,7 +40,7 @@

    Angular versions

      @for (version of versions; track $index) {
    • -
    • @@ -51,7 +51,7 @@

      Angular versions

- @if (from.number >= futureVersion || to.number >= futureVersion) { + @if (from().number >= futureVersion || to().number >= futureVersion) {

Warning: @@ -61,7 +61,7 @@

Angular versions

} - @if (from.number > to.number) { + @if (from().number > to().number) {

Warning: @@ -70,7 +70,7 @@

Angular versions

} - @if (to.number - from.number > 150 && from.number > 240) { + @if (to().number - from().number > 150 && from().number > 240) {

Warning: @@ -84,19 +84,19 @@

Angular versions

Application complexity

Basic Medium Advanced - @if (level === 1) { + @if (level() === 1) {

Shows information for all Angular developers.

- } @else if (level === 2) { + } @else if (level() === 2) {

Shows information that's of interest to more advanced Angular developers.

- } @else if (level === 3) { + } @else if (level() === 3) {

Shows all the information we have about this update.

} @@ -111,7 +111,7 @@

Other dependencies

} - @if (from.number < 600) { + @if (from().number < 600) {

Package Manager

Before you update - } - @if (beforeRecommendations.length <= 0) { + } @empty {
You don't need to do anything before moving between these versions.
@@ -187,8 +186,7 @@

Update to the new version

- } - @if (duringRecommendations.length <= 0) { + } @empty {
There aren't any recommendations for moving between these versions.
@@ -209,8 +207,7 @@

After you update

- } - @if (afterRecommendations.length <= 0) { + } @empty {
You don't need to do anything after moving between these versions.
diff --git a/adev-ja/src/app/features/update/update.component.html b/adev-ja/src/app/features/update/update.component.html index 1b8dca6b4..e7bdc9a50 100644 --- a/adev-ja/src/app/features/update/update.component.html +++ b/adev-ja/src/app/features/update/update.component.html @@ -11,7 +11,7 @@

Angularバージョン

v. @@ -19,7 +19,7 @@

Angularバージョン