From 7daab57af9d148a427cd4b9bb9e70658ebab8770 Mon Sep 17 00:00:00 2001 From: Ricardo Chavarria Date: Mon, 3 Aug 2026 15:49:46 -0600 Subject: [PATCH 1/2] feat: update origin to Angular v22.1 Sync the Spanish translation with Angular v22.1 and fix the accumulated drift between adev-es and the current origin structure. Content sync - Translate/update all guides affected by the v22.1 refactor: signals, components, templates, directives, DI, routing, forms (including the new Signal Forms guides), HTTP client, SSR, testing, Angular Aria, i18n, animations - Fully rewrite guide/testing/components-basics.md and components-scenarios.md to match the current inline-code-based English content (previously referenced a deleted examples/testing app) - Remove content that no longer exists in English (pipes guides, route-animations, prerendering, router-tutorial, devtools, migrations) - Translate events/v22.md and update the aria/*.md Testing and API reference sections to the current format - Fix stale example file references (attribute-directives, router-reference, routing-with-urlmatcher, i18n typos) Link and anchor consistency - Standardize internal links to explicit English heading IDs ({#id}) across guide/ so Spanish translations no longer break anchors, and stabilize the resulting duplicate/misplaced IDs - Fix broken absolute angular.dev links, stale/renamed page targets, and Spanish-slugged anchors found via the real build pipeline (npm run build), not just static link auditing - Verify 1:1 heading structure and .en.md byte-parity for all 341 translated pages, and folder/file parity between adev-es and origin Navigation architecture - Rebuild adev-es/src/app/routing/navigation-entries/index.ts to match origin's new navigation module (previously stuck on the old monolithic sub-navigation-data.ts, missing ~20 new pages and several top-level sections) - Simplify sub-navigation-data.ts back to a thin re-export, matching origin - Translate remaining navigation.component.html/footer.component.html strings and localize the home page demo components (control-flow, deferrable-views, signals demo, hydration demo, animation) Tooling - Fix tools/update-origin.mjs: sync navigation-entries, home page components, and links.ts going forward (previously unmatched glob patterns silently skipped them) - Bump .node-version to satisfy the pinned pnpm version required by CI --- .node-version | 2 +- .../layout/footer/footer.component.en.html | 27 +- .../core/layout/footer/footer.component.html | 4 +- .../navigation/navigation.component.en.html | 70 +- .../navigation/navigation.component.html | 36 +- .../control-flow/control-flow-example.en.html | 27 + .../control-flow/control-flow-example.html | 28 + .../deferrable-views-example.en.html | 38 + .../deferrable-views-example.html | 38 + .../home-animation.component.en.html | 122 ++ .../home-animation.component.html | 123 ++ .../hydration-example.en.html | 216 ++ .../hydration-example/hydration-example.html | 219 ++ .../signals-demo/signals-demo.en.html | 51 + .../components/signals-demo/signals-demo.html | 51 + .../app/features/home/home.component.en.html | 685 ++----- .../src/app/features/home/home.component.html | 687 ++----- .../routing/navigation-entries/index.en.ts | 1823 +++++++++++++++++ .../app/routing/navigation-entries/index.ts | 1823 +++++++++++++++++ .../src/app/routing/sub-navigation-data.en.ts | 1610 +-------------- .../src/app/routing/sub-navigation-data.ts | 1610 +-------------- adev-es/src/content/ai/agent-skills.md | 24 + adev-es/src/content/ai/ai-tutor.en.md | 39 +- adev-es/src/content/ai/ai-tutor.md | 71 +- adev-es/src/content/ai/design-patterns.en.md | 80 +- adev-es/src/content/ai/design-patterns.md | 14 +- adev-es/src/content/ai/develop-with-ai.en.md | 6 +- adev-es/src/content/ai/develop-with-ai.md | 8 +- adev-es/src/content/ai/mcp-server-setup.en.md | 206 +- adev-es/src/content/ai/mcp-server-setup.md | 206 +- adev-es/src/content/ai/overview.en.md | 9 +- adev-es/src/content/ai/overview.md | 20 +- adev-es/src/content/ai/webmcp.md | 259 +++ adev-es/src/content/best-practices/a11y.en.md | 34 +- adev-es/src/content/best-practices/a11y.md | 26 +- .../best-practices/error-handling.en.md | 5 +- .../content/best-practices/error-handling.md | 10 +- .../best-practices/performance/overview.md | 44 + .../profiling-with-chrome-devtools.en.md | 25 +- .../profiling-with-chrome-devtools.md | 34 +- .../skipping-subtrees.en.md | 18 +- .../runtime-performance/skipping-subtrees.md | 14 +- .../slow-computations.en.md | 2 +- .../runtime-performance/slow-computations.md | 6 +- .../runtime-performance/zone-pollution.en.md | 86 +- .../runtime-performance/zone-pollution.md | 4 +- .../content/best-practices/style-guide.en.md | 55 +- .../src/content/best-practices/style-guide.md | 62 +- .../src/content/best-practices/update.en.md | 8 +- adev-es/src/content/best-practices/update.md | 16 +- adev-es/src/content/cli/index.md | 136 -- .../ecosystem/custom-build-pipeline.en.md | 2 +- .../ecosystem/custom-build-pipeline.md | 4 +- .../rxjs-interop/output-interop.en.md | 12 +- .../ecosystem/rxjs-interop/output-interop.md | 4 +- .../rxjs-interop/signals-interop.en.md | 36 +- .../ecosystem/rxjs-interop/signals-interop.md | 24 +- .../rxjs-interop/take-until-destroyed.en.md | 4 +- .../rxjs-interop/take-until-destroyed.md | 2 +- .../ecosystem/service-workers/app-shell.en.md | 4 +- .../service-workers/communications.en.md | 6 +- .../service-workers/communications.md | 12 +- .../ecosystem/service-workers/config.en.md | 17 +- .../ecosystem/service-workers/config.md | 384 ++++ .../custom-service-worker-scripts.en.md | 4 +- .../custom-service-worker-scripts.md | 6 +- .../ecosystem/service-workers/devops.en.md | 30 +- .../ecosystem/service-workers/devops.md | 54 +- .../service-workers/getting-started.en.md | 19 +- .../service-workers/getting-started.md | 28 +- .../ecosystem/service-workers/overview.md | 10 +- .../service-workers/push-notifications.md | 10 +- .../src/content/ecosystem/web-workers.en.md | 33 +- adev-es/src/content/ecosystem/web-workers.md | 2 +- adev-es/src/content/events/v21.md | 16 +- adev-es/src/content/events/v22.en.md | 30 + adev-es/src/content/events/v22.md | 30 + adev-es/src/content/examples/i18n/readme.md | 6 +- .../guide/animations/complex-sequences.en.md | 22 +- .../guide/animations/complex-sequences.md | 30 +- .../src/content/guide/animations/css.en.md | 80 +- adev-es/src/content/guide/animations/css.md | 102 +- .../guide/animations/enter-and-leave.en.md | 22 +- .../guide/animations/enter-and-leave.md | 18 +- .../content/guide/animations/migration.en.md | 122 +- .../src/content/guide/animations/migration.md | 178 +- .../content/guide/animations/overview.en.md | 74 +- .../src/content/guide/animations/overview.md | 52 +- .../animations/reusable-animations.en.md | 14 +- .../guide/animations/reusable-animations.md | 12 +- .../guide/animations/route-animations.en.md | 157 -- .../guide/animations/route-animations.md | 157 -- .../animations/transition-and-triggers.en.md | 54 +- .../animations/transition-and-triggers.md | 86 +- .../src/content/guide/aria/accordion.en.md | 147 +- adev-es/src/content/guide/aria/accordion.md | 155 +- .../src/content/guide/aria/autocomplete.en.md | 87 +- .../src/content/guide/aria/autocomplete.md | 99 +- adev-es/src/content/guide/aria/combobox.en.md | 184 +- adev-es/src/content/guide/aria/combobox.md | 154 +- adev-es/src/content/guide/aria/grid.en.md | 121 +- adev-es/src/content/guide/aria/grid.md | 125 +- adev-es/src/content/guide/aria/listbox.en.md | 121 +- adev-es/src/content/guide/aria/listbox.md | 128 +- adev-es/src/content/guide/aria/menu.en.md | 161 +- adev-es/src/content/guide/aria/menu.md | 177 +- adev-es/src/content/guide/aria/menubar.en.md | 68 +- adev-es/src/content/guide/aria/menubar.md | 80 +- .../src/content/guide/aria/multiselect.en.md | 77 +- adev-es/src/content/guide/aria/multiselect.md | 87 +- adev-es/src/content/guide/aria/overview.en.md | 21 +- adev-es/src/content/guide/aria/overview.md | 26 +- adev-es/src/content/guide/aria/select.en.md | 76 +- adev-es/src/content/guide/aria/select.md | 86 +- adev-es/src/content/guide/aria/tabs.en.md | 133 +- adev-es/src/content/guide/aria/tabs.md | 151 +- adev-es/src/content/guide/aria/toolbar.en.md | 91 +- adev-es/src/content/guide/aria/toolbar.md | 97 +- adev-es/src/content/guide/aria/tree.en.md | 130 +- adev-es/src/content/guide/aria/tree.md | 148 +- .../components/advanced-configuration.en.md | 6 +- .../components/advanced-configuration.md | 2 +- .../components/anatomy-of-components.en.md | 52 +- .../guide/components/anatomy-of-components.md | 8 +- .../guide/components/content-projection.en.md | 52 +- .../guide/components/content-projection.md | 32 +- .../content/guide/components/dom-apis.en.md | 4 +- .../src/content/guide/components/dom-apis.md | 4 +- .../guide/components/host-elements.en.md | 14 +- .../content/guide/components/host-elements.md | 14 +- .../content/guide/components/importing.en.md | 35 - .../src/content/guide/components/importing.md | 35 - .../guide/components/inheritance.en.md | 36 +- .../content/guide/components/inheritance.md | 20 +- .../src/content/guide/components/inputs.en.md | 42 +- .../src/content/guide/components/inputs.md | 40 +- .../content/guide/components/lifecycle.en.md | 6 +- .../src/content/guide/components/lifecycle.md | 20 +- .../guide/components/output-function.en.md | 109 - .../guide/components/output-function.md | 109 - .../content/guide/components/outputs.en.md | 20 +- .../src/content/guide/components/outputs.md | 16 +- .../components/programmatic-rendering.en.md | 211 +- .../components/programmatic-rendering.md | 161 +- .../content/guide/components/queries.en.md | 91 +- .../src/content/guide/components/queries.md | 30 +- .../content/guide/components/selectors.en.md | 26 +- .../src/content/guide/components/selectors.md | 14 +- .../content/guide/components/styling.en.md | 52 +- .../src/content/guide/components/styling.md | 24 +- adev-es/src/content/guide/defer.md | 299 --- .../di/creating-and-using-services.en.md | 123 +- .../guide/di/creating-and-using-services.md | 137 +- .../di/creating-injectable-service.en.md | 106 +- .../guide/di/creating-injectable-service.md | 116 +- .../di/debugging-and-troubleshooting-di.md | 1015 +++++++++ .../di/defining-dependency-providers.en.md | 281 ++- .../guide/di/defining-dependency-providers.md | 74 +- .../di/dependency-injection-context.en.md | 52 +- .../guide/di/dependency-injection-context.md | 28 +- .../src/content/guide/di/di-in-action.en.md | 48 +- adev-es/src/content/guide/di/di-in-action.md | 36 +- .../hierarchical-dependency-injection.en.md | 436 ++-- .../di/hierarchical-dependency-injection.md | 84 +- .../content/guide/di/lazy-loading-services.md | 85 + .../di/lightweight-injection-tokens.en.md | 89 +- .../guide/di/lightweight-injection-tokens.md | 14 +- adev-es/src/content/guide/di/overview.en.md | 62 +- adev-es/src/content/guide/di/overview.md | 38 +- .../directives/attribute-directives.en.md | 145 +- .../guide/directives/attribute-directives.md | 101 +- .../directive-composition-api.en.md | 242 ++- .../directives/directive-composition-api.md | 117 +- .../content/guide/directives/overview.en.md | 143 +- .../src/content/guide/directives/overview.md | 143 +- .../directives/structural-directives.en.md | 96 +- .../guide/directives/structural-directives.md | 34 +- adev-es/src/content/guide/drag-drop.en.md | 168 +- adev-es/src/content/guide/drag-drop.md | 232 +-- adev-es/src/content/guide/elements.en.md | 53 +- adev-es/src/content/guide/elements.md | 31 +- .../content/guide/forms/dynamic-forms.en.md | 6 +- .../src/content/guide/forms/dynamic-forms.md | 20 +- .../content/guide/forms/form-validation.en.md | 114 +- .../content/guide/forms/form-validation.md | 106 +- .../src/content/guide/forms/overview.en.md | 20 +- adev-es/src/content/guide/forms/overview.md | 46 +- .../content/guide/forms/reactive-forms.en.md | 276 ++- .../src/content/guide/forms/reactive-forms.md | 212 +- .../guide/forms/signals/async-operations.md | 792 +++++++ .../guide/forms/signals/comparison.en.md | 21 +- .../content/guide/forms/signals/comparison.md | 22 +- .../guide/forms/signals/cross-field-logic.md | 251 +++ .../guide/forms/signals/custom-controls.en.md | 273 ++- .../guide/forms/signals/custom-controls.md | 210 +- .../signals/designing-your-form-model.md | 422 ++++ .../forms/signals/dynamic-forms-with-json.md | 481 +++++ .../guide/forms/signals/field-metadata.md | 395 ++++ .../signals/field-state-management.en.md | 546 +++-- .../forms/signals/field-state-management.md | 421 ++-- .../content/guide/forms/signals/form-logic.md | 687 +++++++ .../guide/forms/signals/form-submission.md | 327 +++ .../content/guide/forms/signals/migration.md | 516 +++++ .../content/guide/forms/signals/models.en.md | 334 ++- .../src/content/guide/forms/signals/models.md | 254 +-- .../guide/forms/signals/overview.en.md | 20 +- .../content/guide/forms/signals/overview.md | 33 +- .../content/guide/forms/signals/schemas.md | 243 +++ .../content/guide/forms/signals/testing.md | 214 ++ .../guide/forms/signals/validation.en.md | 477 +++-- .../content/guide/forms/signals/validation.md | 268 ++- .../guide/forms/template-driven-forms.en.md | 50 +- .../guide/forms/template-driven-forms.md | 32 +- .../src/content/guide/forms/typed-forms.en.md | 14 +- .../src/content/guide/forms/typed-forms.md | 18 +- .../content/guide/http/http-resource.en.md | 30 +- .../src/content/guide/http/http-resource.md | 8 +- .../src/content/guide/http/interceptors.en.md | 145 +- .../src/content/guide/http/interceptors.md | 34 +- .../content/guide/http/making-requests.en.md | 438 ++-- .../src/content/guide/http/making-requests.md | 46 +- adev-es/src/content/guide/http/overview.en.md | 4 +- adev-es/src/content/guide/http/overview.md | 4 +- adev-es/src/content/guide/http/security.en.md | 12 +- adev-es/src/content/guide/http/security.md | 8 +- adev-es/src/content/guide/http/setup.en.md | 50 +- adev-es/src/content/guide/http/setup.md | 54 +- adev-es/src/content/guide/http/testing.en.md | 52 +- adev-es/src/content/guide/http/testing.md | 32 +- adev-es/src/content/guide/hydration.en.md | 40 +- adev-es/src/content/guide/hydration.md | 68 +- .../src/content/guide/i18n/add-package.en.md | 4 +- adev-es/src/content/guide/i18n/add-package.md | 4 +- adev-es/src/content/guide/i18n/deploy.en.md | 2 +- adev-es/src/content/guide/i18n/deploy.md | 6 +- adev-es/src/content/guide/i18n/example.en.md | 2 +- adev-es/src/content/guide/i18n/example.md | 4 +- .../guide/i18n/format-data-locale.en.md | 18 +- .../content/guide/i18n/format-data-locale.md | 6 +- .../guide/i18n/import-global-variants.en.md | 4 +- .../guide/i18n/import-global-variants.md | 2 +- .../src/content/guide/i18n/locale-id.en.md | 2 +- adev-es/src/content/guide/i18n/locale-id.md | 4 +- .../guide/i18n/manage-marked-text.en.md | 38 +- .../content/guide/i18n/manage-marked-text.md | 10 +- adev-es/src/content/guide/i18n/merge.en.md | 27 +- adev-es/src/content/guide/i18n/merge.md | 20 +- adev-es/src/content/guide/i18n/overview.md | 2 +- adev-es/src/content/guide/i18n/prepare.en.md | 125 +- adev-es/src/content/guide/i18n/prepare.md | 60 +- ...ns-files.en.md => translation-files.en.md} | 140 +- .../content/guide/i18n/translation-files.md | 56 +- .../content/guide/image-optimization.en.md | 127 +- .../src/content/guide/image-optimization.md | 118 +- .../content/guide/incremental-hydration.en.md | 40 +- .../content/guide/incremental-hydration.md | 22 +- .../content/guide/ngmodules/overview.en.md | 56 +- .../src/content/guide/ngmodules/overview.md | 10 +- .../content/guide/pipes/change-detection.md | 114 -- adev-es/src/content/guide/pipes/overview.md | 27 - adev-es/src/content/guide/pipes/precedence.md | 28 - adev-es/src/content/guide/pipes/template.md | 55 - .../src/content/guide/pipes/transform-data.md | 63 - .../pipes/unwrapping-data-observables.md | 15 - adev-es/src/content/guide/prerendering.en.md | 100 - adev-es/src/content/guide/prerendering.md | 99 - .../guide/routing/common-router-tasks.en.md | 79 +- .../guide/routing/common-router-tasks.md | 58 +- .../routing/customizing-route-behavior.en.md | 302 ++- .../routing/customizing-route-behavior.md | 166 +- .../guide/routing/data-resolvers.en.md | 131 +- .../content/guide/routing/data-resolvers.md | 30 +- .../content/guide/routing/define-routes.en.md | 247 +-- .../content/guide/routing/define-routes.md | 141 +- .../guide/routing/lifecycle-and-events.en.md | 90 +- .../guide/routing/lifecycle-and-events.md | 82 +- .../guide/routing/loading-strategies.md | 96 + .../guide/routing/navigate-to-routes.en.md | 111 +- .../guide/routing/navigate-to-routes.md | 118 +- adev-es/src/content/guide/routing/overview.md | 6 +- .../guide/routing/read-route-state.en.md | 122 +- .../content/guide/routing/read-route-state.md | 47 +- .../guide/routing/redirecting-routes.en.md | 30 +- .../guide/routing/redirecting-routes.md | 8 +- .../guide/routing/rendering-strategies.md | 22 +- .../content/guide/routing/route-guards.en.md | 92 +- .../src/content/guide/routing/route-guards.md | 20 +- .../routing/route-transition-animations.en.md | 34 +- .../routing/route-transition-animations.md | 40 +- .../guide/routing/router-reference.en.md | 16 +- .../content/guide/routing/router-reference.md | 14 +- .../guide/routing/router-tutorial.en.md | 276 --- .../content/guide/routing/router-tutorial.md | 276 --- .../routing/routing-with-urlmatcher.en.md | 18 +- .../guide/routing/routing-with-urlmatcher.md | 26 +- .../routing/show-routes-with-outlets.en.md | 77 +- .../guide/routing/show-routes-with-outlets.md | 10 +- .../src/content/guide/routing/testing.en.md | 177 +- adev-es/src/content/guide/routing/testing.md | 14 +- adev-es/src/content/guide/security.en.md | 143 +- adev-es/src/content/guide/security.md | 141 +- .../src/content/guide/signals/debounced.md | 85 + adev-es/src/content/guide/signals/effect.md | 207 ++ .../content/guide/signals/linked-signal.en.md | 100 +- .../content/guide/signals/linked-signal.md | 104 +- .../src/content/guide/signals/overview.en.md | 231 +-- adev-es/src/content/guide/signals/overview.md | 252 +-- .../src/content/guide/signals/resource.en.md | 150 +- adev-es/src/content/guide/signals/resource.md | 154 +- adev-es/src/content/guide/ssr.en.md | 252 ++- adev-es/src/content/guide/ssr.md | 179 +- adev-es/src/content/guide/tailwind.en.md | 17 +- adev-es/src/content/guide/tailwind.md | 16 +- .../src/content/guide/templates/binding.en.md | 23 +- .../src/content/guide/templates/binding.md | 20 +- .../guide/templates/control-flow.en.md | 79 +- .../content/guide/templates/control-flow.md | 63 +- .../src/content/guide/templates/defer.en.md | 80 +- adev-es/src/content/guide/templates/defer.md | 100 +- .../guide/templates/event-listeners.en.md | 58 +- .../guide/templates/event-listeners.md | 28 +- .../guide/templates/expression-syntax.en.md | 48 +- .../guide/templates/expression-syntax.md | 37 +- .../guide/templates/ng-container.en.md | 16 +- .../content/guide/templates/ng-container.md | 12 +- .../content/guide/templates/ng-content.en.md | 24 +- .../content/guide/templates/ng-template.en.md | 89 +- .../content/guide/templates/ng-template.md | 98 +- .../content/guide/templates/overview.en.md | 2 +- .../src/content/guide/templates/overview.md | 6 +- .../src/content/guide/templates/pipes.en.md | 141 +- adev-es/src/content/guide/templates/pipes.md | 124 +- .../guide/templates/two-way-binding.en.md | 56 +- .../guide/templates/two-way-binding.md | 10 +- .../content/guide/templates/variables.en.md | 76 +- .../src/content/guide/templates/variables.md | 56 +- .../content/guide/templates/whitespace.en.md | 10 +- .../src/content/guide/templates/whitespace.md | 6 +- .../guide/testing/attribute-directives.en.md | 166 +- .../guide/testing/attribute-directives.md | 166 +- .../content/guide/testing/code-coverage.en.md | 1 + .../content/guide/testing/code-coverage.md | 8 +- .../component-harnesses-overview.en.md | 4 +- ...omponent-harnesses-testing-environments.md | 12 +- .../guide/testing/components-basics.en.md | 154 +- .../guide/testing/components-basics.md | 256 ++- .../guide/testing/components-scenarios.en.md | 1118 +++++++--- .../guide/testing/components-scenarios.md | 1338 ++++++++---- .../creating-component-harnesses.en.md | 208 +- .../testing/creating-component-harnesses.md | 22 +- .../src/content/guide/testing/debugging.en.md | 14 +- .../src/content/guide/testing/debugging.md | 18 +- adev-es/src/content/guide/testing/karma.en.md | 14 +- adev-es/src/content/guide/testing/karma.md | 22 +- .../guide/testing/migrating-to-vitest.en.md | 21 +- .../guide/testing/migrating-to-vitest.md | 38 +- .../src/content/guide/testing/overview.en.md | 133 +- adev-es/src/content/guide/testing/overview.md | 115 +- adev-es/src/content/guide/testing/pipes.en.md | 51 +- adev-es/src/content/guide/testing/pipes.md | 55 +- .../src/content/guide/testing/services.en.md | 185 +- adev-es/src/content/guide/testing/services.md | 185 +- .../testing/using-component-harnesses.en.md | 84 +- .../testing/using-component-harnesses.md | 24 +- .../content/guide/testing/utility-apis.en.md | 107 +- .../src/content/guide/testing/utility-apis.md | 99 +- .../testing/zone-js-testing-utilities.en.md | 14 +- .../testing/zone-js-testing-utilities.md | 6 +- adev-es/src/content/guide/zoneless.en.md | 46 +- adev-es/src/content/guide/zoneless.md | 26 +- .../introduction/essentials/components.en.md | 16 +- .../introduction/essentials/components.md | 8 +- .../essentials/dependency-injection.en.md | 15 +- .../essentials/dependency-injection.md | 20 +- .../introduction/essentials/next-steps.en.md | 2 +- .../introduction/essentials/next-steps.md | 6 +- .../introduction/essentials/overview.md | 6 +- .../essentials/signal-forms.en.md | 118 +- .../introduction/essentials/signal-forms.md | 127 +- .../introduction/essentials/signals.en.md | 8 +- .../introduction/essentials/signals.md | 6 +- .../introduction/essentials/templates.en.md | 18 +- .../introduction/essentials/templates.md | 12 +- .../content/introduction/installation.en.md | 13 +- .../src/content/introduction/installation.md | 32 +- .../introduction/what-is-angular.en.md | 10 +- .../content/introduction/what-is-angular.md | 4 +- adev-es/src/content/kitchen-sink.en.md | 84 +- adev-es/src/content/kitchen-sink.md | 83 +- adev-es/src/content/reference/cli.md | 2 +- .../configs/angular-compiler-options.en.md | 25 +- .../configs/angular-compiler-options.md | 28 +- .../reference/configs/file-structure.en.md | 12 +- .../reference/configs/file-structure.md | 22 +- .../reference/configs/npm-packages.en.md | 2 +- .../content/reference/configs/npm-packages.md | 2 +- .../reference/configs/workspace-config.en.md | 79 +- .../reference/configs/workspace-config.md | 111 +- .../src/content/reference/errors/NG0100.md | 2 +- .../src/content/reference/errors/NG01002.md | 46 + .../content/reference/errors/NG01101.en.md | 15 +- .../src/content/reference/errors/NG01101.md | 2 +- .../content/reference/errors/NG01203.en.md | 4 +- .../src/content/reference/errors/NG01203.md | 2 +- .../src/content/reference/errors/NG01902.md | 26 + .../src/content/reference/errors/NG0200.md | 2 +- .../src/content/reference/errors/NG0201.en.md | 6 +- .../src/content/reference/errors/NG0201.md | 2 +- .../src/content/reference/errors/NG0203.en.md | 23 +- .../src/content/reference/errors/NG0203.md | 2 +- .../src/content/reference/errors/NG0204.md | 59 + .../src/content/reference/errors/NG0205.md | 70 + .../src/content/reference/errors/NG0207.md | 75 + .../src/content/reference/errors/NG02200.md | 2 +- .../src/content/reference/errors/NG02800.md | 2 +- .../content/reference/errors/NG02802.en.md | 8 +- .../src/content/reference/errors/NG02802.md | 8 +- .../src/content/reference/errors/NG02825.md | 37 + .../src/content/reference/errors/NG0300.en.md | 21 +- .../src/content/reference/errors/NG0300.md | 18 +- .../src/content/reference/errors/NG0301.md | 2 +- .../src/content/reference/errors/NG0302.md | 2 +- .../src/content/reference/errors/NG0401.en.md | 6 +- .../src/content/reference/errors/NG0401.md | 2 +- .../src/content/reference/errors/NG0403.en.md | 6 +- .../src/content/reference/errors/NG0403.md | 4 +- .../src/content/reference/errors/NG0500.en.md | 14 +- .../src/content/reference/errors/NG0500.md | 2 +- .../src/content/reference/errors/NG0501.md | 2 +- .../src/content/reference/errors/NG0502.md | 2 +- .../src/content/reference/errors/NG0503.en.md | 15 +- .../src/content/reference/errors/NG0503.md | 2 +- .../src/content/reference/errors/NG0504.en.md | 21 +- .../src/content/reference/errors/NG0504.md | 6 +- .../src/content/reference/errors/NG0505.md | 2 +- .../src/content/reference/errors/NG0506.en.md | 6 +- .../src/content/reference/errors/NG0506.md | 10 +- .../src/content/reference/errors/NG0507.md | 2 +- .../src/content/reference/errors/NG05101.md | 26 + .../src/content/reference/errors/NG05102.md | 44 + .../content/reference/errors/NG05104.en.md | 12 +- .../src/content/reference/errors/NG05104.md | 2 +- .../src/content/reference/errors/NG05201.md | 53 + .../src/content/reference/errors/NG05703.md | 25 + .../src/content/reference/errors/NG0602.en.md | 2 +- .../src/content/reference/errors/NG0602.md | 4 +- .../src/content/reference/errors/NG0750.md | 2 +- .../src/content/reference/errors/NG0910.en.md | 15 +- .../src/content/reference/errors/NG0910.md | 3 +- .../src/content/reference/errors/NG0912.en.md | 34 +- .../src/content/reference/errors/NG0912.md | 4 +- .../src/content/reference/errors/NG0913.en.md | 7 +- .../src/content/reference/errors/NG0913.md | 17 +- .../src/content/reference/errors/NG0919.md | 99 + .../src/content/reference/errors/NG0950.md | 2 +- .../src/content/reference/errors/NG0951.md | 2 +- .../src/content/reference/errors/NG0955.en.md | 30 +- .../src/content/reference/errors/NG0955.md | 2 +- .../src/content/reference/errors/NG0956.en.md | 28 +- .../src/content/reference/errors/NG0956.md | 2 +- .../src/content/reference/errors/NG1001.md | 2 +- .../src/content/reference/errors/NG2003.md | 2 +- .../src/content/reference/errors/NG2009.md | 2 +- .../src/content/reference/errors/NG3003.en.md | 51 +- .../src/content/reference/errors/NG3003.md | 6 +- .../src/content/reference/errors/NG6100.en.md | 2 +- .../src/content/reference/errors/NG6100.md | 2 +- .../src/content/reference/errors/NG8001.md | 2 +- .../src/content/reference/errors/NG8002.md | 2 +- .../src/content/reference/errors/NG8003.md | 2 +- .../src/content/reference/errors/NG8023.md | 47 + .../src/content/reference/errors/NG8024.md | 51 + .../content/reference/errors/overview.en.md | 15 + .../src/content/reference/errors/overview.md | 19 +- .../extended-diagnostics/NG8021.en.md | 89 +- .../reference/extended-diagnostics/NG8021.md | 44 +- .../extended-diagnostics/NG8101.en.md | 7 +- .../reference/extended-diagnostics/NG8101.md | 8 +- .../extended-diagnostics/NG8102.en.md | 25 +- .../reference/extended-diagnostics/NG8102.md | 8 +- .../extended-diagnostics/NG8103.en.md | 1 - .../reference/extended-diagnostics/NG8103.md | 8 +- .../extended-diagnostics/NG8104.en.md | 2 - .../reference/extended-diagnostics/NG8104.md | 8 +- .../extended-diagnostics/NG8105.en.md | 9 +- .../reference/extended-diagnostics/NG8105.md | 8 +- .../extended-diagnostics/NG8106.en.md | 5 +- .../reference/extended-diagnostics/NG8106.md | 8 +- .../extended-diagnostics/NG8107.en.md | 19 +- .../reference/extended-diagnostics/NG8107.md | 8 +- .../extended-diagnostics/NG8108.en.md | 25 +- .../reference/extended-diagnostics/NG8108.md | 8 +- .../extended-diagnostics/NG8109.en.md | 9 +- .../reference/extended-diagnostics/NG8109.md | 8 +- .../extended-diagnostics/NG8111.en.md | 9 +- .../reference/extended-diagnostics/NG8111.md | 8 +- .../extended-diagnostics/NG8113.en.md | 4 +- .../reference/extended-diagnostics/NG8113.md | 6 +- .../extended-diagnostics/NG8114.en.md | 20 +- .../reference/extended-diagnostics/NG8114.md | 4 +- .../extended-diagnostics/NG8115.en.md | 9 +- .../reference/extended-diagnostics/NG8115.md | 8 +- .../extended-diagnostics/NG8116.en.md | 15 +- .../reference/extended-diagnostics/NG8116.md | 8 +- .../extended-diagnostics/NG8117.en.md | 8 +- .../reference/extended-diagnostics/NG8117.md | 8 +- .../extended-diagnostics/overview.en.md | 1 - .../extended-diagnostics/overview.md | 12 +- adev-es/src/content/reference/license.md | 2 +- .../migrations/cleanup-unused-imports.md | 4 +- .../migrations/common-to-standalone.en.md | 20 +- .../migrations/common-to-standalone.md | 4 +- .../reference/migrations/control-flow.en.md | 8 + .../reference/migrations/control-flow.md | 8 + .../migrations/inject-function.en.md | 48 +- .../reference/migrations/inject-function.md | 20 +- .../migrations/module-with-providers.en.md | 72 - .../migrations/module-with-providers.md | 72 - .../migrations/ngclass-to-class.en.md | 4 +- .../reference/migrations/ngclass-to-class.md | 6 +- .../migrations/ngstyle-to-style.en.md | 4 +- .../reference/migrations/ngstyle-to-style.md | 6 +- .../reference/migrations/outputs.en.md | 8 +- .../content/reference/migrations/outputs.md | 6 +- .../reference/migrations/overview.en.md | 2 +- .../content/reference/migrations/overview.md | 2 +- .../migrations/route-lazy-loading.en.md | 13 +- .../migrations/route-lazy-loading.md | 8 +- .../router-testing-module-migration.en.md | 42 +- .../router-testing-module-migration.md | 8 +- .../migrations/self-closing-tags.en.md | 12 +- .../reference/migrations/self-closing-tags.md | 4 +- .../reference/migrations/signal-inputs.en.md | 30 +- .../reference/migrations/signal-inputs.md | 10 +- .../reference/migrations/signal-queries.en.md | 12 +- .../reference/migrations/signal-queries.md | 10 +- .../reference/migrations/standalone.en.md | 56 +- .../reference/migrations/standalone.md | 26 +- .../reference/migrations/typed-forms.en.md | 43 - .../reference/migrations/typed-forms.md | 43 - adev-es/src/content/reference/press-kit.md | 4 + adev-es/src/content/reference/releases.md | 35 +- adev-es/src/content/reference/roadmap.md | 159 +- adev-es/src/content/reference/versions.en.md | 23 +- adev-es/src/content/reference/versions.md | 14 +- .../src/content/tools/cli/aot-compiler.en.md | 96 +- adev-es/src/content/tools/cli/aot-compiler.md | 44 +- .../tools/cli/aot-metadata-errors.en.md | 76 +- .../content/tools/cli/aot-metadata-errors.md | 82 +- .../tools/cli/build-system-migration.en.md | 22 +- .../tools/cli/build-system-migration.md | 50 +- adev-es/src/content/tools/cli/build.en.md | 68 +- adev-es/src/content/tools/cli/build.md | 14 +- .../src/content/tools/cli/cli-builder.en.md | 59 +- adev-es/src/content/tools/cli/cli-builder.md | 32 +- .../src/content/tools/cli/deployment.en.md | 6 +- adev-es/src/content/tools/cli/deployment.md | 20 +- .../src/content/tools/cli/end-to-end.en.md | 2 +- adev-es/src/content/tools/cli/end-to-end.md | 8 +- .../src/content/tools/cli/environments.en.md | 30 +- adev-es/src/content/tools/cli/environments.md | 6 +- adev-es/src/content/tools/cli/overview.md | 10 +- .../tools/cli/schematics-authoring.en.md | 78 +- .../content/tools/cli/schematics-authoring.md | 28 +- .../tools/cli/schematics-for-libraries.en.md | 54 +- .../tools/cli/schematics-for-libraries.md | 30 +- adev-es/src/content/tools/cli/schematics.md | 6 +- adev-es/src/content/tools/cli/serve.en.md | 13 +- adev-es/src/content/tools/cli/serve.md | 4 +- .../src/content/tools/cli/setup-local.en.md | 2 +- adev-es/src/content/tools/cli/setup-local.md | 18 +- .../tools/cli/template-typecheck.en.md | 34 +- .../content/tools/cli/template-typecheck.md | 26 +- adev-es/src/content/tools/devtools.en.md | 196 -- adev-es/src/content/tools/devtools.md | 196 -- .../src/content/tools/devtools/component.md | 18 +- .../src/content/tools/devtools/injectors.md | 8 +- .../src/content/tools/devtools/overview.en.md | 5 +- .../src/content/tools/devtools/overview.md | 9 +- .../src/content/tools/devtools/profiler.en.md | 2 +- .../src/content/tools/devtools/profiler.md | 12 +- adev-es/src/content/tools/devtools/router.md | 30 + .../src/content/tools/language-service.en.md | 16 +- adev-es/src/content/tools/language-service.md | 20 +- .../libraries/angular-package-format.en.md | 33 +- .../tools/libraries/angular-package-format.md | 70 +- .../tools/libraries/creating-libraries.en.md | 24 +- .../tools/libraries/creating-libraries.md | 30 +- .../src/content/tools/libraries/overview.md | 2 +- .../tools/libraries/using-libraries.en.md | 85 +- .../tools/libraries/using-libraries.md | 14 +- .../deferrable-views/intro/README.md | 2 +- .../1-what-are-deferrable-views/README.en.md | 14 +- .../2-loading-error-placeholder/README.en.md | 49 +- .../2-loading-error-placeholder/README.md | 49 +- .../steps/3-defer-triggers/README.en.md | 44 +- .../steps/3-defer-triggers/README.md | 46 +- .../tutorials/first-app/intro/README.en.md | 2 +- .../tutorials/first-app/intro/README.md | 10 +- .../steps/01-hello-world/README.en.md | 4 +- .../first-app/steps/01-hello-world/README.md | 2 +- .../first-app/steps/02-Home/README.en.md | 64 +- .../first-app/steps/02-Home/README.md | 4 +- .../steps/03-HousingLocation/README.en.md | 16 +- .../steps/03-HousingLocation/README.md | 2 +- .../steps/04-interfaces/README.en.md | 36 +- .../first-app/steps/04-interfaces/README.md | 4 +- .../first-app/steps/05-inputs/README.md | 4 +- .../steps/06-property-binding/README.en.md | 10 +- .../steps/06-property-binding/README.md | 8 +- .../07-dynamic-template-values/README.en.md | 2 +- .../07-dynamic-template-values/README.md | 4 +- .../first-app/steps/08-ngFor/README.md | 4 +- .../first-app/steps/09-services/README.en.md | 30 +- .../first-app/steps/09-services/README.md | 10 +- .../first-app/steps/10-routing/README.en.md | 32 +- .../first-app/steps/10-routing/README.md | 4 +- .../steps/11-details-page/README.en.md | 58 +- .../first-app/steps/11-details-page/README.md | 6 +- .../first-app/steps/12-forms/README.en.md | 38 +- .../first-app/steps/12-forms/README.md | 2 +- .../first-app/steps/13-search/README.en.md | 17 +- .../first-app/steps/13-search/README.md | 2 +- .../first-app/steps/14-http/README.en.md | 50 +- .../first-app/steps/14-http/README.md | 2 +- .../learn-angular/intro/README.en.md | 5 + .../tutorials/learn-angular/intro/README.md | 9 +- .../1-components-in-angular/README.en.md | 2 +- .../steps/10-deferrable-views/README.en.md | 69 +- .../steps/10-deferrable-views/README.md | 2 +- .../steps/11-optimizing-images/README.en.md | 15 +- .../steps/12-enable-routing/README.en.md | 15 +- .../steps/13-define-a-route/README.en.md | 16 +- .../steps/13-define-a-route/README.md | 2 +- .../steps/14-routerLink/README.en.md | 2 +- .../steps/14-routerLink/README.md | 2 +- .../learn-angular/steps/15-forms/README.en.md | 10 +- .../steps/16-form-control-values/README.en.md | 16 +- .../steps/17-reactive-forms/README.en.md | 15 +- .../steps/18-forms-validation/README.en.md | 6 +- .../README.en.md | 26 +- .../README.en.md | 2 +- .../steps/20-inject-based-di/README.en.md | 4 +- .../learn-angular/steps/22-pipes/README.en.md | 24 +- .../steps/23-pipes-format-data/README.en.md | 33 +- .../steps/24-create-a-pipe/README.en.md | 13 +- .../steps/25-next-steps/README.en.md | 5 + .../steps/25-next-steps/README.md | 5 + .../steps/3-composing-components/README.en.md | 13 +- .../steps/4-control-flow-if/README.en.md | 11 +- .../steps/5-control-flow-for/README.en.md | 12 +- .../steps/6-property-binding/README.en.md | 16 +- .../steps/6-property-binding/README.md | 14 +- .../steps/7-event-handling/README.en.md | 3 +- .../learn-angular/steps/8-input/README.en.md | 14 +- .../learn-angular/steps/9-output/README.en.md | 22 +- .../tutorials/signal-forms/intro/README.en.md | 7 +- .../tutorials/signal-forms/intro/README.md | 9 +- .../steps/1-set-up-form-model/README.en.md | 4 +- .../2-connect-form-template/README.en.md | 31 +- .../steps/3-add-validation/README.en.md | 12 +- .../steps/4-display-errors/README.en.md | 8 +- .../steps/5-add-submission/README.en.md | 8 +- .../steps/6-next-steps/README.en.md | 14 +- .../signal-forms/steps/6-next-steps/README.md | 22 +- .../tutorials/signals/intro/README.en.md | 5 + .../content/tutorials/signals/intro/README.md | 7 +- .../1-creating-your-first-signal/README.en.md | 20 +- .../signals/steps/11-next-steps/README.en.md | 5 + .../signals/steps/11-next-steps/README.md | 5 + .../README.en.md | 54 +- .../README.md | 2 +- .../README.en.md | 6 +- .../README.en.md | 15 +- .../README.en.md | 5 +- .../README.en.md | 4 +- .../README.en.md | 20 +- origin | 2 +- tools/update-origin.mjs | 6 +- 679 files changed, 30290 insertions(+), 19171 deletions(-) create mode 100644 adev-es/src/app/features/home/components/control-flow/control-flow-example.en.html create mode 100644 adev-es/src/app/features/home/components/control-flow/control-flow-example.html create mode 100644 adev-es/src/app/features/home/components/deferrable-views-example/deferrable-views-example.en.html create mode 100644 adev-es/src/app/features/home/components/deferrable-views-example/deferrable-views-example.html create mode 100644 adev-es/src/app/features/home/components/home-animation/home-animation.component.en.html create mode 100644 adev-es/src/app/features/home/components/home-animation/home-animation.component.html create mode 100644 adev-es/src/app/features/home/components/hydration-example/hydration-example.en.html create mode 100644 adev-es/src/app/features/home/components/hydration-example/hydration-example.html create mode 100644 adev-es/src/app/features/home/components/signals-demo/signals-demo.en.html create mode 100644 adev-es/src/app/features/home/components/signals-demo/signals-demo.html create mode 100644 adev-es/src/app/routing/navigation-entries/index.en.ts create mode 100644 adev-es/src/app/routing/navigation-entries/index.ts create mode 100644 adev-es/src/content/ai/agent-skills.md create mode 100644 adev-es/src/content/ai/webmcp.md create mode 100644 adev-es/src/content/best-practices/performance/overview.md delete mode 100644 adev-es/src/content/cli/index.md create mode 100644 adev-es/src/content/events/v22.en.md create mode 100644 adev-es/src/content/events/v22.md delete mode 100644 adev-es/src/content/guide/animations/route-animations.en.md delete mode 100644 adev-es/src/content/guide/animations/route-animations.md delete mode 100644 adev-es/src/content/guide/components/importing.en.md delete mode 100644 adev-es/src/content/guide/components/importing.md delete mode 100644 adev-es/src/content/guide/components/output-function.en.md delete mode 100644 adev-es/src/content/guide/components/output-function.md delete mode 100644 adev-es/src/content/guide/defer.md create mode 100644 adev-es/src/content/guide/di/debugging-and-troubleshooting-di.md create mode 100644 adev-es/src/content/guide/di/lazy-loading-services.md create mode 100644 adev-es/src/content/guide/forms/signals/async-operations.md create mode 100644 adev-es/src/content/guide/forms/signals/cross-field-logic.md create mode 100644 adev-es/src/content/guide/forms/signals/designing-your-form-model.md create mode 100644 adev-es/src/content/guide/forms/signals/dynamic-forms-with-json.md create mode 100644 adev-es/src/content/guide/forms/signals/field-metadata.md create mode 100644 adev-es/src/content/guide/forms/signals/form-logic.md create mode 100644 adev-es/src/content/guide/forms/signals/form-submission.md create mode 100644 adev-es/src/content/guide/forms/signals/migration.md create mode 100644 adev-es/src/content/guide/forms/signals/schemas.md create mode 100644 adev-es/src/content/guide/forms/signals/testing.md rename adev-es/src/content/guide/i18n/{translations-files.en.md => translation-files.en.md} (62%) delete mode 100644 adev-es/src/content/guide/pipes/change-detection.md delete mode 100644 adev-es/src/content/guide/pipes/overview.md delete mode 100644 adev-es/src/content/guide/pipes/precedence.md delete mode 100644 adev-es/src/content/guide/pipes/template.md delete mode 100644 adev-es/src/content/guide/pipes/transform-data.md delete mode 100644 adev-es/src/content/guide/pipes/unwrapping-data-observables.md delete mode 100644 adev-es/src/content/guide/prerendering.en.md delete mode 100644 adev-es/src/content/guide/prerendering.md create mode 100644 adev-es/src/content/guide/routing/loading-strategies.md delete mode 100644 adev-es/src/content/guide/routing/router-tutorial.en.md delete mode 100644 adev-es/src/content/guide/routing/router-tutorial.md create mode 100644 adev-es/src/content/guide/signals/debounced.md create mode 100644 adev-es/src/content/guide/signals/effect.md create mode 100644 adev-es/src/content/reference/errors/NG01002.md create mode 100644 adev-es/src/content/reference/errors/NG01902.md create mode 100644 adev-es/src/content/reference/errors/NG0204.md create mode 100644 adev-es/src/content/reference/errors/NG0205.md create mode 100644 adev-es/src/content/reference/errors/NG0207.md create mode 100644 adev-es/src/content/reference/errors/NG02825.md create mode 100644 adev-es/src/content/reference/errors/NG05101.md create mode 100644 adev-es/src/content/reference/errors/NG05102.md create mode 100644 adev-es/src/content/reference/errors/NG05201.md create mode 100644 adev-es/src/content/reference/errors/NG05703.md create mode 100644 adev-es/src/content/reference/errors/NG0919.md create mode 100644 adev-es/src/content/reference/errors/NG8023.md create mode 100644 adev-es/src/content/reference/errors/NG8024.md delete mode 100644 adev-es/src/content/reference/migrations/module-with-providers.en.md delete mode 100644 adev-es/src/content/reference/migrations/module-with-providers.md delete mode 100644 adev-es/src/content/reference/migrations/typed-forms.en.md delete mode 100644 adev-es/src/content/reference/migrations/typed-forms.md delete mode 100644 adev-es/src/content/tools/devtools.en.md delete mode 100644 adev-es/src/content/tools/devtools.md create mode 100644 adev-es/src/content/tools/devtools/router.md diff --git a/.node-version b/.node-version index d135defb..adb55585 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.12.0 \ No newline at end of file +22.14.0 \ No newline at end of file diff --git a/adev-es/src/app/core/layout/footer/footer.component.en.html b/adev-es/src/app/core/layout/footer/footer.component.en.html index 97ef75c6..05c42f91 100644 --- a/adev-es/src/app/core/layout/footer/footer.component.en.html +++ b/adev-es/src/app/core/layout/footer/footer.component.en.html @@ -58,7 +58,7 @@

Community

  • Report Issues @@ -93,28 +93,41 @@

    Resources

    -

    Languages

    +

    Community translations

    - Super-powered by Google ©2010-2025. Code licensed under an + Super-powered by Google ©2010-2026. Code licensed under an MIT-style License . Documentation licensed under CC BY 4.0 - . + . Built by Angular at v{{ angularVersion }}.

    diff --git a/adev-es/src/app/core/layout/footer/footer.component.html b/adev-es/src/app/core/layout/footer/footer.component.html index 0a41a920..3e807ff7 100644 --- a/adev-es/src/app/core/layout/footer/footer.component.html +++ b/adev-es/src/app/core/layout/footer/footer.component.html @@ -32,13 +32,13 @@

    Comunidad

    href="https://angular.lat/coc" title="Tratarnos unos a otros con respeto." > - Codigo de coducta + Código de conducta
  • Reportar problemas diff --git a/adev-es/src/app/core/layout/navigation/navigation.component.en.html b/adev-es/src/app/core/layout/navigation/navigation.component.en.html index 8786877d..00ff93da 100644 --- a/adev-es/src/app/core/layout/navigation/navigation.component.en.html +++ b/adev-es/src/app/core/layout/navigation/navigation.component.en.html @@ -3,7 +3,7 @@ [attr.id]="PRIMARY_NAV_ID" class="wrapper" (docsClickOutside)="closeMobileNav()" - [docsClickOutsideIgnore]="[SECONDARY_NAV_ID]" + [docsClickOutsideIgnore]="[SECONDARY_NAV_ID, SEARCH_DIALOG_ID]" >
    @@ -170,18 +170,13 @@ -
    @@ -451,7 +467,7 @@ @@ -81,7 +81,7 @@ class="adev-nav-item adev-nav-item--logo" [class.adev-nav-item--active]="activeRouteItem() === PAGE_PREFIX.HOME" > - + @if (!isUwu) { - + {{ searchLabel }} K @@ -245,8 +243,8 @@ + + +
    +
    +
    + my-template.html + +
    + + +
    +
    + diff --git a/adev-es/src/app/features/home/components/control-flow/control-flow-example.html b/adev-es/src/app/features/home/components/control-flow/control-flow-example.html new file mode 100644 index 00000000..b15ade7d --- /dev/null +++ b/adev-es/src/app/features/home/components/control-flow/control-flow-example.html @@ -0,0 +1,28 @@ +
    +
    +
    Flujo de Control
    +
    + La nueva sintaxis de flujo de control de Angular hace que las plantillas sean más fáciles de + escribir y entender. +
    + + +
    + +
    +
    +
    + my-template.html + +
    + + +
    +
    +
    diff --git a/adev-es/src/app/features/home/components/deferrable-views-example/deferrable-views-example.en.html b/adev-es/src/app/features/home/components/deferrable-views-example/deferrable-views-example.en.html new file mode 100644 index 00000000..819ce423 --- /dev/null +++ b/adev-es/src/app/features/home/components/deferrable-views-example/deferrable-views-example.en.html @@ -0,0 +1,38 @@ +
    +
    +
    Deferrable Views
    +
    + Deferrable views (also known as @defer blocks) reduce the initial bundle size + of your application by deferring the loading of code that is not strictly necessary for the + initial rendering of a page. +
    + + +
    + +
    +
    +
    + data-visualization-page.html + +
    + + +
    + +
    +
    + data-visualization-page.ts + +
    + + +
    +
    +
    diff --git a/adev-es/src/app/features/home/components/deferrable-views-example/deferrable-views-example.html b/adev-es/src/app/features/home/components/deferrable-views-example/deferrable-views-example.html new file mode 100644 index 00000000..43dff3da --- /dev/null +++ b/adev-es/src/app/features/home/components/deferrable-views-example/deferrable-views-example.html @@ -0,0 +1,38 @@ +
    +
    +
    Vistas Diferibles
    +
    + Las vistas diferibles (también conocidas como bloques @defer) reducen el + tamaño inicial del bundle de tu aplicación al diferir la carga de código que no es + estrictamente necesario para el renderizado inicial de una página. +
    + + +
    + +
    +
    +
    + data-visualization-page.html + +
    + + +
    + +
    +
    + data-visualization-page.ts + +
    + + +
    +
    +
    diff --git a/adev-es/src/app/features/home/components/home-animation/home-animation.component.en.html b/adev-es/src/app/features/home/components/home-animation/home-animation.component.en.html new file mode 100644 index 00000000..ba4628ba --- /dev/null +++ b/adev-es/src/app/features/home/components/home-animation/home-animation.component.en.html @@ -0,0 +1,122 @@ +
    + @if (!isUwu()) { + +
    + + + + + + + + + + + + + + + +
    + } @else { + +
    + Angular logo +
    + } + + +
    +

    Works at any scale

    +

    + Angular lets you start small on a well-lit path and supports you as your team and apps grow. +

    +
    + + + @if (meteorFieldData(); as meteorFieldData) { +
    +
    + @for (type of meteors(); track $index) { +
    + } +
    +
    + } + + +
    +

    Loved by millions

    +

    + Join the millions of developers all over the world building with Angular in a thriving and + friendly community. +

    +
    + + +
    +

    Build for everyone

    +

    + Rely on Angular's built-in hydration, internationalization, security, and accessibility + support to build for everyone around the world. +

    +
    +
    diff --git a/adev-es/src/app/features/home/components/home-animation/home-animation.component.html b/adev-es/src/app/features/home/components/home-animation/home-animation.component.html new file mode 100644 index 00000000..b202bf9e --- /dev/null +++ b/adev-es/src/app/features/home/components/home-animation/home-animation.component.html @@ -0,0 +1,123 @@ +
    + @if (!isUwu()) { + +
    + + + + + + + + + + + + + + + +
    + } @else { + +
    + Angular logo +
    + } + + +
    +

    Funciona a cualquier escala

    +

    + Angular te permite comenzar en pequeño con un camino bien iluminado y te apoya a medida que + tu equipo y tus aplicaciones crecen. +

    +
    + + + @if (meteorFieldData(); as meteorFieldData) { +
    +
    + @for (type of meteors(); track $index) { +
    + } +
    +
    + } + + +
    +

    Amado por millones

    +

    + Únete a los millones de desarrolladores en todo el mundo que construyen con Angular en una + comunidad próspera y amigable. +

    +
    + + +
    +

    Construye para todos

    +

    + Confía en el soporte integrado de Angular para hidratación, internacionalización, seguridad + y accesibilidad para construir para todos en el mundo. +

    +
    +
    diff --git a/adev-es/src/app/features/home/components/hydration-example/hydration-example.en.html b/adev-es/src/app/features/home/components/hydration-example/hydration-example.en.html new file mode 100644 index 00000000..24af1eda --- /dev/null +++ b/adev-es/src/app/features/home/components/hydration-example/hydration-example.en.html @@ -0,0 +1,216 @@ +
    Hydration
    +
    + Hydration is the process that restores the server-side rendered application on the client + +

    + This demo showcases Angular's hydration strategies, allowing you to simulate network latency and + observe how different components hydrate based on their configured triggers. +

    +
    + +
    +
    +
    + + + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + lock + angular.dev/hydration-strategies +
    +
    + + +
    + @if (currentState() === 'Server-Side Rendering...') { +
    +
    +
    +
    +
    +

    Generating SSR Markup...

    +
    + } + +
    +
    +
    +

    Component Strategies

    +

    Mixing eager and deferred hydration triggers.

    +
    +
    +
    Main App
    +
    {{ progress() }}%
    +
    +
    + +
    + @for (card of [card1(), card2(), card3(), card4()]; track $index) { +
    +
    +
    + @if (card.isHydrating) { +
    + } @else { + water_drop + } +
    + @if (card.clickCount > 0) { +
    + CLICKS: {{ card.clickCount }} +
    + } +
    + +
    +
    {{ card.title }}
    +

    {{ card.description }}

    +
    + + + + @if (card.syntax) { +
    + {{ card.syntax }} +
    + } +
    + } +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    Eager Hydration
    +

    + Essential components (like navigation or top-fold content) hydrate as soon as the main + application bundle is ready. +

    +
    +
    +
    Incremental Hydration
    +

    + Use hydration triggers like on interaction or on timer to delay + JS execution for non-critical elements. +

    +
    +
    +
    Event Replay
    +

    + User clicks are never lost. Angular records events during the "Static" phase and + re-dispatches them once hydrated. +

    +
    +
    +
    + +
    diff --git a/adev-es/src/app/features/home/components/hydration-example/hydration-example.html b/adev-es/src/app/features/home/components/hydration-example/hydration-example.html new file mode 100644 index 00000000..7e825cdd --- /dev/null +++ b/adev-es/src/app/features/home/components/hydration-example/hydration-example.html @@ -0,0 +1,219 @@ +
    Hidratación
    +
    + La hidratación es el proceso que restaura en el cliente la aplicación renderizada del lado del + servidor + +

    + Esta demo muestra las estrategias de hidratación de Angular, permitiéndote simular latencia de + red y observar cómo diferentes componentes se hidratan según sus disparadores configurados. +

    +
    + +
    +
    +
    + + + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    + lock + angular.dev/hydration-strategies +
    +
    + + +
    + @if (currentState() === 'Server-Side Rendering...') { +
    +
    +
    +
    +
    +

    Generando marcado SSR...

    +
    + } + +
    +
    +
    +

    Estrategias de Componentes

    +

    + Combinando disparadores de hidratación inmediata y diferida. +

    +
    +
    +
    Aplicación Principal
    +
    {{ progress() }}%
    +
    +
    + +
    + @for (card of [card1(), card2(), card3(), card4()]; track $index) { +
    +
    +
    + @if (card.isHydrating) { +
    + } @else { + water_drop + } +
    + @if (card.clickCount > 0) { +
    + CLICS: {{ card.clickCount }} +
    + } +
    + +
    +
    {{ card.title }}
    +

    {{ card.description }}

    +
    + + + + @if (card.syntax) { +
    + {{ card.syntax }} +
    + } +
    + } +
    +
    +
    + + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    Hidratación Inmediata
    +

    + Los componentes esenciales (como la navegación o el contenido visible sin desplazamiento) + se hidratan tan pronto como el bundle principal de la aplicación está listo. +

    +
    +
    +
    Hidratación Incremental
    +

    + Usa disparadores de hidratación como on interaction u + on timer para retrasar la ejecución de JS de elementos no críticos. +

    +
    +
    +
    Reproducción de Eventos
    +

    + Los clics del usuario nunca se pierden. Angular registra los eventos durante la fase + "Estática" y los vuelve a emitir una vez hidratado. +

    +
    +
    +
    + +
    diff --git a/adev-es/src/app/features/home/components/signals-demo/signals-demo.en.html b/adev-es/src/app/features/home/components/signals-demo/signals-demo.en.html new file mode 100644 index 00000000..19575c5c --- /dev/null +++ b/adev-es/src/app/features/home/components/signals-demo/signals-demo.en.html @@ -0,0 +1,51 @@ +
    +
    +
    Signals: Fine-Grained Reactivity
    +
    + Type in the search box to filter the list. A + computed signal automatically recalculates the + filteredItems whenever the searchTerm signal changes. +
    + +
    + +
    + @for (item of filteredItems(); track item) { +
    {{ item }}
    + } +
    +
    + + +
    + +
    +
    +
    + component.ts + +
    + + +
    + +
    +
    + component.html + +
    + +
    +
    +
    diff --git a/adev-es/src/app/features/home/components/signals-demo/signals-demo.html b/adev-es/src/app/features/home/components/signals-demo/signals-demo.html new file mode 100644 index 00000000..fd606174 --- /dev/null +++ b/adev-es/src/app/features/home/components/signals-demo/signals-demo.html @@ -0,0 +1,51 @@ +
    +
    +
    Signals: Reactividad de Grano Fino
    +
    + Escribe en el cuadro de búsqueda para filtrar la lista. Un signal + computed recalcula automáticamente + filteredItems cada vez que el signal searchTerm cambia. +
    + +
    + +
    + @for (item of filteredItems(); track item) { +
    {{ item }}
    + } +
    +
    + + +
    + +
    +
    +
    + component.ts + +
    + + +
    + +
    +
    + component.html + +
    + +
    +
    +
    diff --git a/adev-es/src/app/features/home/home.component.en.html b/adev-es/src/app/features/home/home.component.en.html index cdeb3ffa..45b0809e 100644 --- a/adev-es/src/app/features/home/home.component.en.html +++ b/adev-es/src/app/features/home/home.component.en.html @@ -1,493 +1,242 @@
    -
    - @if (!isUwu) { - -
    - - - - - - - - - - - - - - - -

    The framework for building scalable web apps with confidence

    -
    - } @else { - -
    - Angular logo +
    +
    + - } -
    - - - -
    + @if (!isUwu) { +
    +

    Productivity
    Meets scalability

    +

    The framework for building scalable web apps with confidence

    +
    + } @else { + +
    + Angular logo +
    + } + +
    -
    -

    Productivity meets scalability

    -
    -
    - - - - - - - - - - -

    AI-forward

    -

    Resources and integrations to supercharge your development with AI

    -
    -
    - - - -

    Opinionated & versatile

    -

    Organized yet modular thanks to Angular components and dependency injection

    -
    -
    - - - -

    Reactive

    -

    Fast state updates with fine-grained reactivity based on Angular Signals

    +
    +

    Features that actually
    help you solve problems

    + +
    +
    +
    +
    Signals
    +
    Control Flow
    +
    Deferrable Views
    +
    Hydration
    -
    - - - -

    Fully featured

    -

    - Everything works together with Angular's first-party modules for forms, routing, and more -

    + +
    +
    + @defer (on idle) { + + + + } +
    + +
    + @defer (on idle) { + + + + } +
    + +
    + @defer (on idle) { + + + + } +
    + +
    + @defer (on idle) { + + + + } +
    - +
    +

    Enabling you to build
    smarter and faster

    + +
    -
    -
    -
    -

    When performance matters

    -

    +

    +
    +
    + -
    - + + + Get started +
    - +
    +
    +

    Want to learn more about Angular?

    +
    +
    -
    -
    -

    Want to learn more?

    -
    - - - - -
    -

    New to Angular?

    -

    - Try our completely in browser tutorial lessons designed to give you hands on - experience with Angular. -

    -

    - Recommended for developers new to Angular looking to dive right into the code -

    - Start coding -
    -
    - - - - -
    -

    More of a reader?

    -

    - Our essentials guides are designed to help you understand Angular fundamentals in the - time it would take to finish a cup of coffee (or tea). -

    -

    - Recommended for developers with framework experience but want quick overview of - concepts -

    - Angular concepts -
    -
    - - -
    -

    Why Angular?

    -

    Learn about Angular, its benefits, and if it's right for you.

    -

    - Recommended for developers seeking to learn more about the Angular framework -

    - About Angular -
    -
    - - - - - - - - - +
    + +
    +
    +
    +

    Want to learn more about Angular?

    +
    diff --git a/adev-es/src/app/features/home/home.component.html b/adev-es/src/app/features/home/home.component.html index 0b95118a..31812d15 100644 --- a/adev-es/src/app/features/home/home.component.html +++ b/adev-es/src/app/features/home/home.component.html @@ -1,493 +1,242 @@
    -
    - @if (!isUwu) { - -
    - - - - - - - - - - - - - - - -

    El framework para crear aplicaciones web escalables con confianza

    -
    - } @else { - -
    - Angular logo +
    +
    + - } -
    - - - -
    + @if (!isUwu) { +
    +

    Productividad
    con escalabilidad

    +

    El framework para crear aplicaciones web escalables con confianza

    +
    + } @else { + +
    + Angular logo +
    + } + +
    -
    -

    Productividad y escalabilidad

    -
    -
    - - - - - - - - - - -

    Impulsado por IA

    -

    Recursos e integraciones para potenciar tu desarrollo con IA

    -
    -
    - - - -

    Estructurado & versátil

    -

    Organizado y modular gracias a los componentes y la inyección de dependencias de Angular

    -
    -
    - - - -

    Reactivo

    -

    Actualizaciones de estado rápidas con reactividad basada en Angular Signals

    +
    +

    Características que realmente
    te ayudan a resolver problemas

    + +
    +
    +
    +
    Signals
    +
    Control Flow
    +
    Deferrable Views
    +
    Hydration
    -
    - - - -

    Completamente equipado

    -

    - Todo funciona en conjunto con los módulos nativos de Angular para formularios, ruteo y más -

    + +
    +
    + @defer (on idle) { + + + + } +
    + +
    + @defer (on idle) { + + + + } +
    + +
    + @defer (on idle) { + + + + } +
    + +
    + @defer (on idle) { + + + + } +
    - +
    +

    Facilita el desarrollo de
    aplicaciones más inteligentes y rápidas

    +
    + +
    + auto_awesome +
    +
    +

    + Con IA integrada, recursos e integraciones para potenciar tu desarrollo + con IA +

    +
    +
    + + +
    + grid_view +
    +
    +

    + Estructurado y versátil, organizado y modular gracias a los componentes + y la inyección de dependencias de Angular +

    +
    +
    -
    -
    + +
    +
    +
    +
    +

    Donde el rendimiento importa

    +

    + Respaldado por millones para desarrollar aplicaciones rápidas y fiables que se adaptan + al tamaño de tu equipo +

    +
    - +
    +
    +

    ¿Quieres aprender más sobre Angular?

    +
    +
    -
    -
    -

    ¿Quieres aprender más?

    -
    - - - - -
    -

    ¿Nuevo en Angular?

    -

    - Prueba nuestras lecciones tutoriales completamente en el navegador, diseñadas para que adquieras - experiencia práctica con Angular. -

    -

    - Recomendado para desarrolladores nuevos en Angular que quieran sumergirse directamente en el código -

    - Comienza a programar -
    -
    - - - - -
    -

    ¿Prefieres leer?

    -

    - Nuestros guías esenciales están diseñadas para ayudarte a comprender los fundamentos de Angular - en el tiempo que tomaría terminar una taza de café (o té). -

    -

    - Recomendado para desarrolladores con experiencia en frameworks que quieren un repaso - rápido de los conceptos -

    - Conceptos de Angular -
    -
    - - -
    -

    ¿Por qué Angular?

    -

    Aprende sobre Angular, sus beneficios y si es adecuado para ti.

    -

    - Recomendado para desarrolladores que quieren aprender más sobre el framework Angular -

    - Acerca de Angular -
    -
    - - - - - - - - - +
    + +
    +
    +
    +

    ¿Quieres aprender más sobre Angular?

    +
    diff --git a/adev-es/src/app/routing/navigation-entries/index.en.ts b/adev-es/src/app/routing/navigation-entries/index.en.ts new file mode 100644 index 00000000..c9dc4f01 --- /dev/null +++ b/adev-es/src/app/routing/navigation-entries/index.en.ts @@ -0,0 +1,1823 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {isDevMode} from '@angular/core'; +import type {NavigationItem} from '@angular/docs'; +// These imports are expected to be red because they are generated a build time +// @ts-ignore +import ERRORS_NAV_DATA from '../../../content/reference/errors/routes.json' with {type: 'json'}; +// @ts-ignore +import EXT_DIAGNOSTICS_NAV_DATA from '../../../content/reference/extended-diagnostics/routes.json' with {type: 'json'}; +// @ts-ignore +import FIRST_APP_TUTORIAL_NAV_DATA from '../../../content/tutorials/first-app/first-app/routes.json' with {type: 'json'}; +// @ts-ignore +import LEARN_ANGULAR_TUTORIAL_NAV_DATA from '../../../content/tutorials/learn-angular/learn-angular/routes.json' with {type: 'json'}; +// @ts-ignore +import DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA from '../../../content/tutorials/deferrable-views/deferrable-views/routes.json' with {type: 'json'}; +// @ts-ignore +import SIGNALS_TUTORIAL_NAV_DATA from '../../../content/tutorials/signals/signals/routes.json' with {type: 'json'}; +// @ts-ignore +import SIGNAL_FORMS_TUTORIAL_NAV_DATA from '../../../content/tutorials/signal-forms/signal-forms/routes.json' with {type: 'json'}; +// @ts-ignore +import API_MANIFEST_JSON from '../../../assets/manifest.json' with {type: 'json'}; + +interface SubNavigationData { + docs: NavigationItem[]; + reference: NavigationItem[]; + tutorials: NavigationItem[]; + footer: NavigationItem[]; +} + +export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [ + { + label: 'Introduction', + children: [ + { + label: 'What is Angular?', + path: 'overview', + contentPath: 'introduction/what-is-angular', + }, + { + label: 'Installation', + path: 'installation', + contentPath: 'introduction/installation', + }, + { + label: 'Essentials', + children: [ + { + label: 'Overview', + path: 'essentials', + contentPath: 'introduction/essentials/overview', + }, + { + label: 'Composition with components', + path: 'essentials/components', + contentPath: 'introduction/essentials/components', + }, + { + label: 'Reactivity with signals', + path: 'essentials/signals', + contentPath: 'introduction/essentials/signals', + }, + { + label: 'Dynamic interfaces with templates', + path: 'essentials/templates', + contentPath: 'introduction/essentials/templates', + }, + { + label: 'Forms with signals', + path: 'essentials/signal-forms', + contentPath: 'introduction/essentials/signal-forms', + status: 'new', + }, + { + label: 'Modular design with dependency injection', + path: 'essentials/dependency-injection', + contentPath: 'introduction/essentials/dependency-injection', + }, + { + label: 'Next Steps', + path: 'essentials/next-steps', + contentPath: 'introduction/essentials/next-steps', + }, + ], + }, + { + label: 'Start coding! 🚀', + path: 'tutorials/learn-angular', + }, + ], + }, + { + label: 'In-depth Guides', + children: [ + { + label: 'Signals', + status: 'updated', + children: [ + { + label: 'Overview', + path: 'guide/signals', + contentPath: 'guide/signals/overview', + }, + { + label: 'Dependent state with linkedSignal', + path: 'guide/signals/linked-signal', + contentPath: 'guide/signals/linked-signal', + }, + { + label: 'Async reactivity with resources', + path: 'guide/signals/resource', + contentPath: 'guide/signals/resource', + }, + { + label: 'Debounced signals', + path: 'guide/signals/debounced', + contentPath: 'guide/signals/debounced', + status: 'new', + }, + { + label: 'Side effects for non-reactives APIs', + path: 'guide/signals/effect', + contentPath: 'guide/signals/effect', + status: 'new', + }, + ], + }, + { + label: 'Components', + children: [ + { + label: 'Anatomy of components', + path: 'guide/components', + contentPath: 'guide/components/anatomy-of-components', + }, + { + label: 'Selectors', + path: 'guide/components/selectors', + contentPath: 'guide/components/selectors', + }, + { + label: 'Styling', + path: 'guide/components/styling', + contentPath: 'guide/components/styling', + }, + { + label: 'Accepting data with input properties', + path: 'guide/components/inputs', + contentPath: 'guide/components/inputs', + }, + { + label: 'Custom events with outputs', + path: 'guide/components/outputs', + contentPath: 'guide/components/outputs', + }, + { + label: 'Content projection with ng-content', + path: 'guide/components/content-projection', + contentPath: 'guide/components/content-projection', + }, + { + label: 'Host elements', + path: 'guide/components/host-elements', + contentPath: 'guide/components/host-elements', + }, + { + label: 'Lifecycle', + path: 'guide/components/lifecycle', + contentPath: 'guide/components/lifecycle', + }, + { + label: 'Referencing component children with queries', + path: 'guide/components/queries', + contentPath: 'guide/components/queries', + }, + { + label: 'Using DOM APIs', + path: 'guide/components/dom-apis', + contentPath: 'guide/components/dom-apis', + }, + { + label: 'Inheritance', + path: 'guide/components/inheritance', + contentPath: 'guide/components/inheritance', + }, + { + label: 'Programmatically rendering components', + path: 'guide/components/programmatic-rendering', + contentPath: 'guide/components/programmatic-rendering', + }, + { + label: 'Advanced configuration', + path: 'guide/components/advanced-configuration', + contentPath: 'guide/components/advanced-configuration', + }, + { + label: 'Custom Elements', + path: 'guide/elements', + contentPath: 'guide/elements', + }, + ], + }, + { + label: 'Templates', + children: [ + { + label: 'Overview', + path: 'guide/templates', + contentPath: 'guide/templates/overview', + }, + { + label: 'Binding dynamic text, properties and attributes', + path: 'guide/templates/binding', + contentPath: 'guide/templates/binding', + }, + { + label: 'Adding event listeners', + path: 'guide/templates/event-listeners', + contentPath: 'guide/templates/event-listeners', + }, + { + label: 'Two-way binding', + path: 'guide/templates/two-way-binding', + contentPath: 'guide/templates/two-way-binding', + }, + { + label: 'Control flow', + path: 'guide/templates/control-flow', + contentPath: 'guide/templates/control-flow', + }, + { + label: 'Pipes', + path: 'guide/templates/pipes', + contentPath: 'guide/templates/pipes', + }, + { + label: 'Slotting child content with ng-content', + path: 'guide/templates/ng-content', + contentPath: 'guide/templates/ng-content', + }, + { + label: 'Create template fragments with ng-template', + path: 'guide/templates/ng-template', + contentPath: 'guide/templates/ng-template', + }, + { + label: 'Grouping elements with ng-container', + path: 'guide/templates/ng-container', + contentPath: 'guide/templates/ng-container', + }, + { + label: 'Variables in templates', + path: 'guide/templates/variables', + contentPath: 'guide/templates/variables', + }, + { + label: 'Deferred loading with @defer', + path: 'guide/templates/defer', + contentPath: 'guide/templates/defer', + }, + { + label: 'Expression syntax', + path: 'guide/templates/expression-syntax', + contentPath: 'guide/templates/expression-syntax', + }, + { + label: 'Whitespace in templates', + path: 'guide/templates/whitespace', + contentPath: 'guide/templates/whitespace', + }, + ], + }, + { + label: 'Directives', + children: [ + { + label: 'Overview', + path: 'guide/directives', + contentPath: 'guide/directives/overview', + }, + { + label: 'Attribute directives', + path: 'guide/directives/attribute-directives', + contentPath: 'guide/directives/attribute-directives', + }, + { + label: 'Structural directives', + path: 'guide/directives/structural-directives', + contentPath: 'guide/directives/structural-directives', + }, + { + label: 'Directive composition API', + path: 'guide/directives/directive-composition-api', + contentPath: 'guide/directives/directive-composition-api', + }, + { + label: 'Optimizing images with NgOptimizedImage', + path: 'guide/image-optimization', + contentPath: 'guide/image-optimization', + }, + ], + }, + { + label: 'Dependency Injection', + status: 'updated', + children: [ + { + label: 'Overview', + path: 'guide/di', + contentPath: 'guide/di/overview', + }, + { + label: 'Creating and using services', + path: 'guide/di/creating-and-using-services', + contentPath: 'guide/di/creating-and-using-services', + }, + { + label: 'Lazy loading services', + path: 'guide/di/lazy-loading-services', + contentPath: 'guide/di/lazy-loading-services', + status: 'new', + }, + { + label: 'Defining dependency providers', + path: 'guide/di/defining-dependency-providers', + contentPath: 'guide/di/defining-dependency-providers', + }, + { + label: 'Injection context', + path: 'guide/di/dependency-injection-context', + contentPath: 'guide/di/dependency-injection-context', + }, + { + label: 'Hierarchical injectors', + path: 'guide/di/hierarchical-dependency-injection', + contentPath: 'guide/di/hierarchical-dependency-injection', + }, + { + label: 'Optimizing injection tokens', + path: 'guide/di/lightweight-injection-tokens', + contentPath: 'guide/di/lightweight-injection-tokens', + }, + { + label: 'DI in action', + path: 'guide/di/di-in-action', + contentPath: 'guide/di/di-in-action', + }, + { + label: 'Debugging and troubleshooting DI', + path: 'guide/di/debugging-and-troubleshooting-di', + contentPath: 'guide/di/debugging-and-troubleshooting-di', + status: 'new', + }, + ], + }, + { + label: 'Routing', + children: [ + { + label: 'Overview', + path: 'guide/routing', + contentPath: 'guide/routing/overview', + }, + { + label: 'Define routes', + path: 'guide/routing/define-routes', + contentPath: 'guide/routing/define-routes', + }, + { + label: 'Route Loading Strategies', + path: 'guide/routing/loading-strategies', + contentPath: 'guide/routing/loading-strategies', + }, + { + label: 'Show routes with Outlets', + path: 'guide/routing/show-routes-with-outlets', + contentPath: 'guide/routing/show-routes-with-outlets', + }, + { + label: 'Navigate to routes', + path: 'guide/routing/navigate-to-routes', + contentPath: 'guide/routing/navigate-to-routes', + }, + { + label: 'Read route state', + path: 'guide/routing/read-route-state', + contentPath: 'guide/routing/read-route-state', + }, + { + label: 'Redirecting routes', + path: 'guide/routing/redirecting-routes', + contentPath: 'guide/routing/redirecting-routes', + }, + { + label: 'Control route access with guards', + path: 'guide/routing/route-guards', + contentPath: 'guide/routing/route-guards', + }, + { + label: 'Route data resolvers', + path: 'guide/routing/data-resolvers', + contentPath: 'guide/routing/data-resolvers', + }, + { + label: 'Lifecycle and events', + path: 'guide/routing/lifecycle-and-events', + contentPath: 'guide/routing/lifecycle-and-events', + }, + { + label: 'Testing routing and navigation', + path: 'guide/routing/testing', + contentPath: 'guide/routing/testing', + }, + { + label: 'Other routing tasks', + path: 'guide/routing/common-router-tasks', + contentPath: 'guide/routing/common-router-tasks', + }, + { + label: 'Creating custom route matches', + path: 'guide/routing/routing-with-urlmatcher', + contentPath: 'guide/routing/routing-with-urlmatcher', + }, + { + label: 'Rendering strategies', + path: 'guide/routing/rendering-strategies', + contentPath: 'guide/routing/rendering-strategies', + }, + { + label: 'Customizing route behavior', + path: 'guide/routing/customizing-route-behavior', + contentPath: 'guide/routing/customizing-route-behavior', + }, + { + label: 'Router reference', + path: 'guide/routing/router-reference', + contentPath: 'guide/routing/router-reference', + }, + { + label: 'Route transition animations', + path: 'guide/routing/route-transition-animations', + contentPath: 'guide/routing/route-transition-animations', + }, + ], + }, + { + label: 'Forms', + status: 'updated', + preserveOtherCategoryOrder: true, + categoriesStatus: [ + { + 'Signal Forms': 'new', + }, + ], + children: [ + { + label: 'Overview', + path: 'guide/forms', + contentPath: 'guide/forms/overview', + }, + + { + label: 'Overview', + path: 'guide/forms/signals/overview', + contentPath: 'guide/forms/signals/overview', + category: 'Signal Forms', + }, + { + label: 'Form models', + path: 'guide/forms/signals/models', + contentPath: 'guide/forms/signals/models', + category: 'Signal Forms', + }, + { + label: 'Form model design', + path: 'guide/forms/signals/model-design', + contentPath: 'guide/forms/signals/designing-your-form-model', + category: 'Signal Forms', + }, + { + label: 'Field state management', + path: 'guide/forms/signals/field-state-management', + contentPath: 'guide/forms/signals/field-state-management', + category: 'Signal Forms', + }, + { + label: 'Validation', + path: 'guide/forms/signals/validation', + contentPath: 'guide/forms/signals/validation', + category: 'Signal Forms', + }, + { + label: 'Form logic', + path: 'guide/forms/signals/form-logic', + contentPath: 'guide/forms/signals/form-logic', + category: 'Signal Forms', + }, + { + label: 'Cross-field logic', + path: 'guide/forms/signals/cross-field-logic', + contentPath: 'guide/forms/signals/cross-field-logic', + category: 'Signal Forms', + }, + { + label: 'Form submission', + path: 'guide/forms/signals/form-submission', + contentPath: 'guide/forms/signals/form-submission', + category: 'Signal Forms', + }, + { + label: 'Schemas', + path: 'guide/forms/signals/schemas', + contentPath: 'guide/forms/signals/schemas', + category: 'Signal Forms', + }, + { + label: 'Field metadata', + path: 'guide/forms/signals/field-metadata', + contentPath: 'guide/forms/signals/field-metadata', + category: 'Signal Forms', + }, + { + label: 'Async operations', + path: 'guide/forms/signals/async-operations', + contentPath: 'guide/forms/signals/async-operations', + category: 'Signal Forms', + }, + { + label: 'Dynamic forms with JSON', + path: 'guide/forms/signals/dynamic-forms-with-json', + contentPath: 'guide/forms/signals/dynamic-forms-with-json', + category: 'Signal Forms', + }, + { + label: 'Custom controls', + path: 'guide/forms/signals/custom-controls', + contentPath: 'guide/forms/signals/custom-controls', + category: 'Signal Forms', + }, + { + label: 'Testing', + path: 'guide/forms/signals/testing', + contentPath: 'guide/forms/signals/testing', + category: 'Signal Forms', + }, + { + label: 'Comparison with other form systems', + path: 'guide/forms/signals/comparison', + contentPath: 'guide/forms/signals/comparison', + category: 'Signal Forms', + }, + { + label: 'Migrating from Reactive Forms', + path: 'guide/forms/signals/migration', + contentPath: 'guide/forms/signals/migration', + category: 'Signal Forms', + }, + { + label: 'Reactive forms', + path: 'guide/forms/reactive-forms', + contentPath: 'guide/forms/reactive-forms', + category: 'Reactive Forms', + }, + { + label: 'Strictly typed reactive forms', + path: 'guide/forms/typed-forms', + contentPath: 'guide/forms/typed-forms', + category: 'Reactive Forms', + }, + { + label: 'Template-driven forms', + path: 'guide/forms/template-driven-forms', + contentPath: 'guide/forms/template-driven-forms', + category: 'Template driven Forms', + }, + { + label: 'Validate form input', + path: 'guide/forms/form-validation', + contentPath: 'guide/forms/form-validation', + category: 'Reactive Forms', + }, + { + label: 'Validate form input', + path: 'guide/forms/form-validation', + contentPath: 'guide/forms/form-validation', + category: 'Template driven Forms', + }, + { + label: 'Building dynamic forms', + path: 'guide/forms/dynamic-forms', + contentPath: 'guide/forms/dynamic-forms', + category: 'Reactive Forms', + }, + ], + }, + { + label: 'HTTP Client', + children: [ + { + label: 'Overview', + path: 'guide/http', + contentPath: 'guide/http/overview', + }, + { + label: 'Setting up HttpClient', + path: 'guide/http/setup', + contentPath: 'guide/http/setup', + }, + { + label: 'Making requests', + path: 'guide/http/making-requests', + contentPath: 'guide/http/making-requests', + }, + { + label: 'Reactive data fetching with httpResource', + path: 'guide/http/http-resource', + contentPath: 'guide/http/http-resource', + }, + { + label: 'Intercepting requests and responses', + path: 'guide/http/interceptors', + contentPath: 'guide/http/interceptors', + }, + { + label: 'Testing', + path: 'guide/http/testing', + contentPath: 'guide/http/testing', + }, + ], + }, + { + label: 'Server-side & hybrid-rendering', + children: [ + { + label: 'Overview', + path: 'guide/performance', + contentPath: 'guide/performance/overview', + }, + { + label: 'Server-side and hybrid-rendering', + path: 'guide/ssr', + contentPath: 'guide/ssr', + }, + { + label: 'Hydration', + path: 'guide/hydration', + contentPath: 'guide/hydration', + }, + { + label: 'Incremental Hydration', + path: 'guide/incremental-hydration', + contentPath: 'guide/incremental-hydration', + }, + ], + }, + { + label: 'Testing', + children: [ + { + label: 'Overview', + path: 'guide/testing', + contentPath: 'guide/testing/overview', + }, + { + label: 'Basics of testing components', + path: 'guide/testing/components-basics', + contentPath: 'guide/testing/components-basics', + }, + { + label: 'Component testing scenarios', + path: 'guide/testing/components-scenarios', + contentPath: 'guide/testing/components-scenarios', + }, + { + label: 'Testing services', + path: 'guide/testing/services', + contentPath: 'guide/testing/services', + }, + { + label: 'Testing attribute directives', + path: 'guide/testing/attribute-directives', + contentPath: 'guide/testing/attribute-directives', + }, + { + label: 'Testing pipes', + path: 'guide/testing/pipes', + contentPath: 'guide/testing/pipes', + }, + { + label: 'Testing routing and navigation', + path: 'guide/routing/testing', + contentPath: 'guide/routing/testing', + status: 'new', + isCrossReferenced: true, + }, + { + label: 'Debugging tests', + path: 'guide/testing/debugging', + contentPath: 'guide/testing/debugging', + }, + { + label: 'Code coverage', + path: 'guide/testing/code-coverage', + contentPath: 'guide/testing/code-coverage', + }, + { + label: 'Testing utility APIs', + path: 'guide/testing/utility-apis', + contentPath: 'guide/testing/utility-apis', + }, + { + label: 'Component harnesses overview', + path: 'guide/testing/component-harnesses-overview', + contentPath: 'guide/testing/component-harnesses-overview', + }, + { + label: 'Using component harnesses in tests', + path: 'guide/testing/using-component-harnesses', + contentPath: 'guide/testing/using-component-harnesses', + }, + { + label: 'Creating harnesses for your components', + path: 'guide/testing/creating-component-harnesses', + contentPath: 'guide/testing/creating-component-harnesses', + }, + { + label: 'Adding harness support for additional testing environments', + path: 'guide/testing/component-harnesses-testing-environments', + contentPath: 'guide/testing/component-harnesses-testing-environments', + }, + { + label: 'Migrating from Karma to Vitest', + path: 'guide/testing/migrating-to-vitest', + contentPath: 'guide/testing/migrating-to-vitest', + }, + { + label: 'Testing with Karma and Jasmine', + path: 'guide/testing/karma', + contentPath: 'guide/testing/karma', + }, + { + label: 'Zone.js Testing Utilities', + path: 'guide/testing/zone-js-testing-utilities', + contentPath: 'guide/testing/zone-js-testing-utilities', + }, + ], + }, + { + label: 'Angular Aria', + status: 'new', + children: [ + { + label: 'Overview', + path: 'guide/aria/overview', + contentPath: 'guide/aria/overview', + }, + { + label: 'Accordion', + path: 'guide/aria/accordion', + contentPath: 'guide/aria/accordion', + }, + { + label: 'Autocomplete', + path: 'guide/aria/autocomplete', + contentPath: 'guide/aria/autocomplete', + }, + { + label: 'Combobox', + path: 'guide/aria/combobox', + contentPath: 'guide/aria/combobox', + }, + { + label: 'Grid', + path: 'guide/aria/grid', + contentPath: 'guide/aria/grid', + }, + { + label: 'Listbox', + path: 'guide/aria/listbox', + contentPath: 'guide/aria/listbox', + }, + { + label: 'Menu', + path: 'guide/aria/menu', + contentPath: 'guide/aria/menu', + }, + { + label: 'Menubar', + path: 'guide/aria/menubar', + contentPath: 'guide/aria/menubar', + }, + { + label: 'Multiselect', + path: 'guide/aria/multiselect', + contentPath: 'guide/aria/multiselect', + }, + { + label: 'Select', + path: 'guide/aria/select', + contentPath: 'guide/aria/select', + }, + { + label: 'Tabs', + path: 'guide/aria/tabs', + contentPath: 'guide/aria/tabs', + }, + { + label: 'Toolbar', + path: 'guide/aria/toolbar', + contentPath: 'guide/aria/toolbar', + }, + { + label: 'Tree', + path: 'guide/aria/tree', + contentPath: 'guide/aria/tree', + }, + ], + }, + { + label: 'Internationalization', + children: [ + { + label: 'Overview', + path: 'guide/i18n', + contentPath: 'guide/i18n/overview', + }, + { + label: 'Add the localize package', + path: 'guide/i18n/add-package', + contentPath: 'guide/i18n/add-package', + }, + { + label: 'Refer to locales by ID', + path: 'guide/i18n/locale-id', + contentPath: 'guide/i18n/locale-id', + }, + { + label: 'Format data based on locale', + path: 'guide/i18n/format-data-locale', + contentPath: 'guide/i18n/format-data-locale', + }, + { + label: 'Prepare component for translation', + path: 'guide/i18n/prepare', + contentPath: 'guide/i18n/prepare', + }, + { + label: 'Work with translation files', + path: 'guide/i18n/translation-files', + contentPath: 'guide/i18n/translation-files', + }, + { + label: 'Merge translations into the app', + path: 'guide/i18n/merge', + contentPath: 'guide/i18n/merge', + }, + { + label: 'Deploy multiple locales', + path: 'guide/i18n/deploy', + contentPath: 'guide/i18n/deploy', + }, + { + label: 'Import global variants of the locale data', + path: 'guide/i18n/import-global-variants', + contentPath: 'guide/i18n/import-global-variants', + }, + { + label: 'Manage marked text with custom IDs', + path: 'guide/i18n/manage-marked-text', + contentPath: 'guide/i18n/manage-marked-text', + }, + { + label: 'Example Angular application', + path: 'guide/i18n/example', + contentPath: 'guide/i18n/example', + }, + ], + }, + { + label: 'Animations', + children: [ + { + label: 'Enter and Leave animations', + path: 'guide/animations', + contentPath: 'guide/animations/enter-and-leave', + }, + { + label: 'Complex Animations with CSS', + path: 'guide/animations/css', + contentPath: 'guide/animations/css', + }, + { + label: 'Route transition animations', + path: 'guide/routing/route-transition-animations', + contentPath: 'guide/routing/route-transition-animations', + isCrossReferenced: true, + }, + ], + }, + { + label: 'Drag and drop', + path: 'guide/drag-drop', + contentPath: 'guide/drag-drop', + }, + ], + }, + { + label: 'Build with AI', + children: [ + { + label: 'Get Started', + path: 'ai', + contentPath: 'ai/overview', + }, + { + label: 'LLM prompts and AI IDE setup', + path: 'ai/develop-with-ai', + contentPath: 'ai/develop-with-ai', + }, + { + label: 'Agent Skills', + path: 'ai/agent-skills', + contentPath: 'ai/agent-skills', + status: 'new', + }, + { + label: 'Angular CLI MCP Server setup', + path: 'ai/mcp', + contentPath: 'ai/mcp-server-setup', + }, + { + label: 'Angular AI Tutor', + path: 'ai/ai-tutor', + contentPath: 'ai/ai-tutor', + }, + { + label: 'Design Patterns', + path: 'ai/design-patterns', + contentPath: 'ai/design-patterns', + }, + { + label: 'WebMCP', + path: 'ai/webmcp', + contentPath: 'ai/webmcp', + status: 'new', + }, + ], + }, + { + label: 'Developer Tools', + children: [ + { + label: 'Angular CLI', + children: [ + { + label: 'Overview', + path: 'tools/cli', + contentPath: 'tools/cli/overview', + }, + { + label: 'Local set-up', + path: 'tools/cli/setup-local', + contentPath: 'tools/cli/setup-local', + }, + { + label: 'Building Angular apps', + path: 'tools/cli/build', + contentPath: 'tools/cli/build', + }, + { + label: 'Serving Angular apps for development', + path: 'tools/cli/serve', + contentPath: 'tools/cli/serve', + }, + { + label: 'Deployment', + path: 'tools/cli/deployment', + contentPath: 'tools/cli/deployment', + }, + { + label: 'End-to-End Testing', + path: 'tools/cli/end-to-end', + contentPath: 'tools/cli/end-to-end', + }, + { + label: 'Migrating to new build system', + path: 'tools/cli/build-system-migration', + contentPath: 'tools/cli/build-system-migration', + }, + { + label: 'Build environments', + path: 'tools/cli/environments', + contentPath: 'tools/cli/environments', + }, + { + label: 'Angular CLI builders', + path: 'tools/cli/cli-builder', + contentPath: 'tools/cli/cli-builder', + }, + { + label: 'Generating code using schematics', + path: 'tools/cli/schematics', + contentPath: 'tools/cli/schematics', + }, + { + label: 'Authoring schematics', + path: 'tools/cli/schematics-authoring', + contentPath: 'tools/cli/schematics-authoring', + }, + { + label: 'Schematics for libraries', + path: 'tools/cli/schematics-for-libraries', + contentPath: 'tools/cli/schematics-for-libraries', + }, + { + label: 'Template type checking', + path: 'tools/cli/template-typecheck', + contentPath: 'tools/cli/template-typecheck', + }, + { + label: 'Ahead-of-time (AOT) compilation', + path: 'tools/cli/aot-compiler', + contentPath: 'tools/cli/aot-compiler', + }, + { + label: 'AOT metadata errors', + path: 'tools/cli/aot-metadata-errors', + contentPath: 'tools/cli/aot-metadata-errors', + }, + ], + }, + { + label: 'Libraries', + children: [ + { + label: 'Overview', + path: 'tools/libraries', + contentPath: 'tools/libraries/overview', + }, + { + label: 'Creating Libraries', + path: 'tools/libraries/creating-libraries', + contentPath: 'tools/libraries/creating-libraries', + }, + { + label: 'Using Libraries', + path: 'tools/libraries/using-libraries', + contentPath: 'tools/libraries/using-libraries', + }, + { + label: 'Angular Package Format', + path: 'tools/libraries/angular-package-format', + contentPath: 'tools/libraries/angular-package-format', + }, + ], + }, + { + label: 'DevTools', + children: [ + { + label: 'Overview', + path: 'tools/devtools', + contentPath: 'tools/devtools/overview', + }, + { + label: 'Components', + path: 'tools/devtools/component', + contentPath: 'tools/devtools/component', + }, + { + label: 'Profiler', + path: 'tools/devtools/profiler', + contentPath: 'tools/devtools/profiler', + }, + { + label: 'Injectors', + path: 'tools/devtools/injectors', + contentPath: 'tools/devtools/injectors', + }, + // TODO: create those guides + // The signal debugging docs should also be added to the signal section + // label: 'Signals', + // path: 'tools/devtools/signals', + // contentPath: 'tools/devtools/signals', + // }, + { + label: 'Router Tree', + path: 'tools/devtools/router', + contentPath: 'tools/devtools/router', + }, + ], + }, + { + label: 'Language Service', + path: 'tools/language-service', + contentPath: 'tools/language-service', + }, + ], + }, + { + label: 'Best Practices', + children: [ + { + label: 'Style Guide', + path: 'style-guide', + contentPath: 'best-practices/style-guide', + }, + { + label: 'Security', + path: 'best-practices/security', + contentPath: 'guide/security', // Have not refactored due to build issues + }, + { + label: 'Accessibility', + path: 'best-practices/a11y', + contentPath: 'best-practices/a11y', + }, + { + label: 'Unhandled errors in Angular', + path: 'best-practices/error-handling', + contentPath: 'best-practices/error-handling', + }, + { + label: 'Performance', + preserveOtherCategoryOrder: true, + children: [ + { + label: 'Overview', + path: 'best-practices/performance', + contentPath: 'best-practices/performance/overview', + }, + + // Loading Performance + { + label: 'Lazy-loaded routes', + path: 'best-practices/performance/lazy-loaded-routes', + contentPath: 'guide/routing/loading-strategies', + category: 'Loading Performance', + }, + { + label: 'Deferred loading with @defer', + path: 'best-practices/performance/defer', + contentPath: 'guide/templates/defer', + category: 'Loading Performance', + }, + { + label: 'Lazy loading services', + path: 'best-practices/performance/lazy-loading-services', + contentPath: 'guide/di/lazy-loading-services', + category: 'Loading Performance', + }, + { + label: 'Image optimization', + path: 'best-practices/performance/image-optimization', + contentPath: 'guide/image-optimization', + category: 'Loading Performance', + }, + { + label: 'Server-side rendering', + path: 'best-practices/performance/ssr', + contentPath: 'guide/ssr', + category: 'Loading Performance', + }, + + // Runtime Performance + { + label: 'Overview', + path: 'best-practices/runtime-performance', + contentPath: 'best-practices/runtime-performance/overview', + category: 'Runtime Performance', + }, + { + label: 'Zoneless', + path: 'guide/zoneless', + contentPath: 'guide/zoneless', + category: 'Runtime Performance', + }, + { + label: 'Slow computations', + path: 'best-practices/slow-computations', + contentPath: 'best-practices/runtime-performance/slow-computations', + category: 'Runtime Performance', + }, + { + label: 'Skipping component subtrees', + path: 'best-practices/skipping-subtrees', + contentPath: 'best-practices/runtime-performance/skipping-subtrees', + category: 'Runtime Performance', + }, + { + label: 'Zone pollution', + path: 'best-practices/zone-pollution', + contentPath: 'best-practices/runtime-performance/zone-pollution', + category: 'Runtime Performance', + }, + + { + label: 'Chrome DevTools profiling', + path: 'best-practices/profiling-with-chrome-devtools', + contentPath: 'best-practices/runtime-performance/profiling-with-chrome-devtools', + category: 'Runtime Performance', + }, + ], + }, + { + label: 'Keeping up-to-date', + path: 'update', + contentPath: 'best-practices/update', + }, + ], + }, + { + label: 'Developer Events', + children: [ + { + label: 'Angular v22 Release', + path: 'events/v22', + contentPath: 'events/v22', + status: 'new', + }, + { + label: 'Angular v21 Release', + path: 'events/v21', + contentPath: 'events/v21', + }, + ], + }, + { + label: 'Extended Ecosystem', + children: [ + { + label: 'NgModules', + path: 'guide/ngmodules/overview', + contentPath: 'guide/ngmodules/overview', + }, + { + label: 'Legacy Animations', + children: [ + { + label: 'Overview', + path: 'guide/legacy-animations', + contentPath: 'guide/animations/overview', + }, + { + label: 'Transition and Triggers', + path: 'guide/legacy-animations/transition-and-triggers', + contentPath: 'guide/animations/transition-and-triggers', + }, + { + label: 'Complex Sequences', + path: 'guide/legacy-animations/complex-sequences', + contentPath: 'guide/animations/complex-sequences', + }, + { + label: 'Reusable Animations', + path: 'guide/legacy-animations/reusable-animations', + contentPath: 'guide/animations/reusable-animations', + }, + { + label: 'Migrating to Native CSS Animations', + path: 'guide/animations/migration', + contentPath: 'guide/animations/migration', + }, + ], + }, + { + label: 'Using RxJS with Angular', + children: [ + { + label: 'Signals interop', + path: 'ecosystem/rxjs-interop', + contentPath: 'ecosystem/rxjs-interop/signals-interop', + }, + { + label: 'Component output interop', + path: 'ecosystem/rxjs-interop/output-interop', + contentPath: 'ecosystem/rxjs-interop/output-interop', + }, + { + label: 'Unsubscribing with takeUntilDestroyed', + path: 'ecosystem/rxjs-interop/take-until-destroyed', + contentPath: 'ecosystem/rxjs-interop/take-until-destroyed', + }, + ], + }, + { + label: 'Service Workers & PWAs', + children: [ + { + label: 'Overview', + path: 'ecosystem/service-workers', + contentPath: 'ecosystem/service-workers/overview', + }, + { + label: 'Getting started', + path: 'ecosystem/service-workers/getting-started', + contentPath: 'ecosystem/service-workers/getting-started', + }, + { + label: 'Custom service worker scripts', + path: 'ecosystem/service-workers/custom-service-worker-scripts', + contentPath: 'ecosystem/service-workers/custom-service-worker-scripts', + }, + { + label: 'Configuration file', + path: 'ecosystem/service-workers/config', + contentPath: 'ecosystem/service-workers/config', + }, + { + label: 'Communicating with the service worker', + path: 'ecosystem/service-workers/communications', + contentPath: 'ecosystem/service-workers/communications', + }, + { + label: 'Push notifications', + path: 'ecosystem/service-workers/push-notifications', + contentPath: 'ecosystem/service-workers/push-notifications', + }, + { + label: 'Service worker devops', + path: 'ecosystem/service-workers/devops', + contentPath: 'ecosystem/service-workers/devops', + }, + { + label: 'App shell pattern', + path: 'ecosystem/service-workers/app-shell', + contentPath: 'ecosystem/service-workers/app-shell', + }, + ], + }, + { + label: 'Web workers', + path: 'ecosystem/web-workers', + contentPath: 'ecosystem/web-workers', + }, + { + label: 'Custom build pipeline', + path: 'ecosystem/custom-build-pipeline', + contentPath: 'ecosystem/custom-build-pipeline', + }, + { + label: 'Tailwind', + path: 'guide/tailwind', + contentPath: 'guide/tailwind', + }, + { + label: 'Angular Fire', + path: 'https://github.com/angular/angularfire#readme', + }, + { + label: 'Google Maps', + path: 'https://github.com/angular/components/tree/main/src/google-maps#readme', + }, + { + label: 'Google Pay', + path: 'https://github.com/google-pay/google-pay-button#angular', + }, + { + label: 'YouTube player', + path: 'https://github.com/angular/components/blob/main/src/youtube-player/README.md', + }, + { + label: 'Angular CDK', + path: 'https://material.angular.dev/cdk/categories', + }, + { + label: 'Angular Material', + path: 'https://material.angular.dev/', + }, + ], + }, + ...(isDevMode() + ? [ + { + label: 'Adev Dev Guide', + children: [ + { + label: 'Kitchen Sink', + path: 'kitchen-sink', + contentPath: 'kitchen-sink', + }, + ], + }, + ] + : []), +]; + +export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [ + FIRST_APP_TUTORIAL_NAV_DATA, + LEARN_ANGULAR_TUTORIAL_NAV_DATA, + DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA, + SIGNALS_TUTORIAL_NAV_DATA, + SIGNAL_FORMS_TUTORIAL_NAV_DATA, + { + path: 'tutorials', + contentPath: 'tutorials/home', + label: 'Tutorials', + }, +]; + +export const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [ + { + label: 'Roadmap', + path: 'roadmap', + contentPath: 'reference/roadmap', + }, + { + label: 'Get involved', + path: 'https://github.com/angular/angular/blob/main/CONTRIBUTING.md', + }, + { + label: 'API Reference', + preserveOtherCategoryOrder: true, + children: [ + { + label: 'Overview', + path: 'api', + }, + ...getApiNavigationItems(), + ], + }, + { + label: 'CLI Reference', + children: [ + { + label: 'Overview', + path: 'cli', + contentPath: 'reference/cli', + }, + { + label: 'ng add', + path: 'cli/add', + }, + { + label: 'ng analytics', + children: [ + { + label: 'Overview', + path: 'cli/analytics', + }, + { + label: 'disable', + path: 'cli/analytics/disable', + }, + { + label: 'enable', + path: 'cli/analytics/enable', + }, + { + label: 'info', + path: 'cli/analytics/info', + }, + { + label: 'prompt', + path: 'cli/analytics/prompt', + }, + ], + }, + { + label: 'ng build', + path: 'cli/build', + }, + { + label: 'ng cache', + children: [ + { + label: 'Overview', + path: 'cli/cache', + }, + { + label: 'clean', + path: 'cli/cache/clean', + }, + { + label: 'disable', + path: 'cli/cache/disable', + }, + { + label: 'enable', + path: 'cli/cache/enable', + }, + { + label: 'info', + path: 'cli/cache/info', + }, + ], + }, + { + label: 'ng completion', + children: [ + { + label: 'Overview', + path: 'cli/completion', + }, + { + label: 'script', + path: 'cli/completion/script', + }, + ], + }, + { + label: 'ng config', + path: 'cli/config', + }, + { + label: 'ng deploy', + path: 'cli/deploy', + }, + { + label: 'ng e2e', + path: 'cli/e2e', + }, + { + label: 'ng extract-i18n', + path: 'cli/extract-i18n', + }, + { + label: 'ng generate', + children: [ + { + label: 'Overview', + path: 'cli/generate', + }, + { + label: 'ai-config', + path: 'cli/generate/ai-config', + }, + { + label: 'app-shell', + path: 'cli/generate/app-shell', + }, + { + label: 'application', + path: 'cli/generate/application', + }, + { + label: 'class', + path: 'cli/generate/class', + }, + { + label: 'component', + path: 'cli/generate/component', + }, + { + label: 'config', + path: 'cli/generate/config', + }, + { + label: 'directive', + path: 'cli/generate/directive', + }, + { + label: 'enum', + path: 'cli/generate/enum', + }, + { + label: 'environments', + path: 'cli/generate/environments', + }, + { + label: 'guard', + path: 'cli/generate/guard', + }, + { + label: 'interceptor', + path: 'cli/generate/interceptor', + }, + { + label: 'interface', + path: 'cli/generate/interface', + }, + { + label: 'library', + path: 'cli/generate/library', + }, + { + label: 'module', + path: 'cli/generate/module', + }, + { + label: 'pipe', + path: 'cli/generate/pipe', + }, + { + label: 'resolver', + path: 'cli/generate/resolver', + }, + { + label: 'service-worker', + path: 'cli/generate/service-worker', + }, + { + label: 'service', + path: 'cli/generate/service', + }, + { + label: 'web-worker', + path: 'cli/generate/web-worker', + }, + ], + }, + { + label: 'ng lint', + path: 'cli/lint', + }, + { + label: 'ng new', + path: 'cli/new', + }, + { + label: 'ng run', + path: 'cli/run', + }, + { + label: 'ng serve', + path: 'cli/serve', + }, + { + label: 'ng test', + path: 'cli/test', + }, + { + label: 'ng update', + path: 'cli/update', + }, + { + label: 'ng version', + path: 'cli/version', + }, + ], + }, + { + label: 'Error Encyclopedia', + children: [ + { + label: 'Overview', + path: 'errors', + contentPath: 'reference/errors/overview', + }, + ...ERRORS_NAV_DATA, + ], + }, + { + label: 'Extended Diagnostics', + children: [ + { + label: 'Overview', + path: 'extended-diagnostics', + contentPath: 'reference/extended-diagnostics/overview', + }, + ...EXT_DIAGNOSTICS_NAV_DATA, + ], + }, + { + label: 'Versioning and releases', + path: 'reference/releases', + contentPath: 'reference/releases', + }, + { + label: 'Version compatibility', + path: 'reference/versions', + contentPath: 'reference/versions', + }, + { + label: 'Update guide', + path: 'update-guide', + }, + { + label: 'Configurations', + children: [ + { + label: 'File structure', + path: 'reference/configs/file-structure', + contentPath: 'reference/configs/file-structure', + }, + { + label: 'Workspace configuration', + path: 'reference/configs/workspace-config', + contentPath: 'reference/configs/workspace-config', + }, + { + label: 'Angular compiler options', + path: 'reference/configs/angular-compiler-options', + contentPath: 'reference/configs/angular-compiler-options', + }, + { + label: 'npm dependencies', + path: 'reference/configs/npm-packages', + contentPath: 'reference/configs/npm-packages', + }, + ], + }, + { + label: 'Migrations', + children: [ + { + label: 'Overview', + path: 'reference/migrations', + contentPath: 'reference/migrations/overview', + }, + { + label: 'Standalone', + path: 'reference/migrations/standalone', + contentPath: 'reference/migrations/standalone', + }, + { + label: 'Control Flow Syntax', + path: 'reference/migrations/control-flow', + contentPath: 'reference/migrations/control-flow', + }, + { + label: 'inject() Function', + path: 'reference/migrations/inject-function', + contentPath: 'reference/migrations/inject-function', + }, + { + label: 'Lazy-loaded routes', + path: 'reference/migrations/route-lazy-loading', + contentPath: 'reference/migrations/route-lazy-loading', + }, + { + label: 'Signal inputs', + path: 'reference/migrations/signal-inputs', + contentPath: 'reference/migrations/signal-inputs', + }, + { + label: 'Outputs', + path: 'reference/migrations/outputs', + contentPath: 'reference/migrations/outputs', + }, + { + label: 'Signal queries', + path: 'reference/migrations/signal-queries', + contentPath: 'reference/migrations/signal-queries', + }, + { + label: 'Clean up unused imports', + path: 'reference/migrations/cleanup-unused-imports', + contentPath: 'reference/migrations/cleanup-unused-imports', + }, + { + label: 'Self-closing tags', + path: 'reference/migrations/self-closing-tags', + contentPath: 'reference/migrations/self-closing-tags', + }, + { + label: 'NgClass to Class', + path: 'reference/migrations/ngclass-to-class', + contentPath: 'reference/migrations/ngclass-to-class', + }, + { + label: 'NgStyle to Style', + path: 'reference/migrations/ngstyle-to-style', + contentPath: 'reference/migrations/ngstyle-to-style', + }, + { + label: 'Router Testing Module Migration', + path: 'reference/migrations/router-testing-module-migration', + contentPath: 'reference/migrations/router-testing-module-migration', + }, + { + label: 'CommonModule to Standalone', + path: 'reference/migrations/common-to-standalone', + contentPath: 'reference/migrations/common-to-standalone', + }, + ], + }, +]; + +export const FOOTER_NAVIGATION_DATA: NavigationItem[] = [ + { + label: 'Press Kit', + path: 'press-kit', + contentPath: 'reference/press-kit', + }, + { + label: 'License', + path: 'license', + contentPath: 'reference/license', + }, +]; + +export const ALL_ITEMS = [ + ...DOCS_SUB_NAVIGATION_DATA, + ...REFERENCE_SUB_NAVIGATION_DATA, + ...FOOTER_NAVIGATION_DATA, + ...TUTORIALS_SUB_NAVIGATION_DATA, +]; + +function getApiNavigationItems(): NavigationItem[] { + const manifest = API_MANIFEST_JSON as any; // TODO(mri): Use proper type when the refactoring of #66252 gets in. + + const apiNavigationItems: NavigationItem[] = []; + + for (const packageEntry of manifest) { + const packageNavigationItem: NavigationItem = { + label: packageEntry.moduleLabel, + children: packageEntry.entries.map((api: any) => ({ + path: getApiUrl(packageEntry, api.name), + label: api.name, + category: api.category, + })), + }; + + apiNavigationItems.push(packageNavigationItem); + } + + return apiNavigationItems; +} + +function getApiUrl(packageEntry: any, apiName: string): string { + const packageName = packageEntry.normalizedModuleName + // packages like `angular_core` should be `core` + // packages like `angular_animation_browser` should be `animation/browser` + .replace('angular_', '') + .replaceAll('_', '/'); + return `api/${packageName}/${apiName}`; +} diff --git a/adev-es/src/app/routing/navigation-entries/index.ts b/adev-es/src/app/routing/navigation-entries/index.ts new file mode 100644 index 00000000..8ded3db3 --- /dev/null +++ b/adev-es/src/app/routing/navigation-entries/index.ts @@ -0,0 +1,1823 @@ +/*! + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {isDevMode} from '@angular/core'; +import type {NavigationItem} from '@angular/docs'; +// These imports are expected to be red because they are generated a build time +// @ts-ignore +import ERRORS_NAV_DATA from '../../../content/reference/errors/routes.json' with {type: 'json'}; +// @ts-ignore +import EXT_DIAGNOSTICS_NAV_DATA from '../../../content/reference/extended-diagnostics/routes.json' with {type: 'json'}; +// @ts-ignore +import FIRST_APP_TUTORIAL_NAV_DATA from '../../../content/tutorials/first-app/first-app/routes.json' with {type: 'json'}; +// @ts-ignore +import LEARN_ANGULAR_TUTORIAL_NAV_DATA from '../../../content/tutorials/learn-angular/learn-angular/routes.json' with {type: 'json'}; +// @ts-ignore +import DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA from '../../../content/tutorials/deferrable-views/deferrable-views/routes.json' with {type: 'json'}; +// @ts-ignore +import SIGNALS_TUTORIAL_NAV_DATA from '../../../content/tutorials/signals/signals/routes.json' with {type: 'json'}; +// @ts-ignore +import SIGNAL_FORMS_TUTORIAL_NAV_DATA from '../../../content/tutorials/signal-forms/signal-forms/routes.json' with {type: 'json'}; +// @ts-ignore +import API_MANIFEST_JSON from '../../../assets/manifest.json' with {type: 'json'}; + +interface SubNavigationData { + docs: NavigationItem[]; + reference: NavigationItem[]; + tutorials: NavigationItem[]; + footer: NavigationItem[]; +} + +export const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [ + { + label: 'Introducción', + children: [ + { + label: '¿Qué es Angular?', + path: 'overview', + contentPath: 'introduction/what-is-angular', + }, + { + label: 'Instalación', + path: 'installation', + contentPath: 'introduction/installation', + }, + { + label: 'Esenciales', + children: [ + { + label: 'Visión general', + path: 'essentials', + contentPath: 'introduction/essentials/overview', + }, + { + label: 'Composición basada en componentes', + path: 'essentials/components', + contentPath: 'introduction/essentials/components', + }, + { + label: 'Reactividad con signals', + path: 'essentials/signals', + contentPath: 'introduction/essentials/signals', + }, + { + label: 'Interfaces dinámicas con plantillas', + path: 'essentials/templates', + contentPath: 'introduction/essentials/templates', + }, + { + label: 'Formularios con signals', + path: 'essentials/signal-forms', + contentPath: 'introduction/essentials/signal-forms', + status: 'new', + }, + { + label: 'Diseño modular con inyección de dependencias', + path: 'essentials/dependency-injection', + contentPath: 'introduction/essentials/dependency-injection', + }, + { + label: 'Siguientes pasos', + path: 'essentials/next-steps', + contentPath: 'introduction/essentials/next-steps', + }, + ], + }, + { + label: 'Start coding! 🚀', + path: 'tutorials/learn-angular', + }, + ], + }, + { + label: 'Guías Detalladas', + children: [ + { + label: 'Signals', + status: 'updated', + children: [ + { + label: 'Visión general', + path: 'guide/signals', + contentPath: 'guide/signals/overview', + }, + { + label: 'Estado dependiente con linkedSignal', + path: 'guide/signals/linked-signal', + contentPath: 'guide/signals/linked-signal', + }, + { + label: 'Reactividad asíncrona con resource', + path: 'guide/signals/resource', + contentPath: 'guide/signals/resource', + }, + { + label: 'Signals con debounce', + path: 'guide/signals/debounced', + contentPath: 'guide/signals/debounced', + status: 'new', + }, + { + label: 'Efectos secundarios para APIs no reactivas', + path: 'guide/signals/effect', + contentPath: 'guide/signals/effect', + status: 'new', + }, + ], + }, + { + label: 'Componentes', + children: [ + { + label: 'Anatomía de Componentes', + path: 'guide/components', + contentPath: 'guide/components/anatomy-of-components', + }, + { + label: 'Selectores', + path: 'guide/components/selectors', + contentPath: 'guide/components/selectors', + }, + { + label: 'Estilos', + path: 'guide/components/styling', + contentPath: 'guide/components/styling', + }, + { + label: 'Aceptando datos con propiedades de input', + path: 'guide/components/inputs', + contentPath: 'guide/components/inputs', + }, + { + label: 'Eventos personalizados con outputs', + path: 'guide/components/outputs', + contentPath: 'guide/components/outputs', + }, + { + label: 'Proyección de contenido con ng-content', + path: 'guide/components/content-projection', + contentPath: 'guide/components/content-projection', + }, + { + label: 'Elementos host de componentes', + path: 'guide/components/host-elements', + contentPath: 'guide/components/host-elements', + }, + { + label: 'Ciclo de vida del componente', + path: 'guide/components/lifecycle', + contentPath: 'guide/components/lifecycle', + }, + { + label: 'Referenciando hijos de componentes con consultas', + path: 'guide/components/queries', + contentPath: 'guide/components/queries', + }, + { + label: 'Usando APIs del DOM', + path: 'guide/components/dom-apis', + contentPath: 'guide/components/dom-apis', + }, + { + label: 'Herencia', + path: 'guide/components/inheritance', + contentPath: 'guide/components/inheritance', + }, + { + label: 'Renderizado programático de componentes', + path: 'guide/components/programmatic-rendering', + contentPath: 'guide/components/programmatic-rendering', + }, + { + label: 'Configuración avanzada', + path: 'guide/components/advanced-configuration', + contentPath: 'guide/components/advanced-configuration', + }, + { + label: 'Elementos personalizados', + path: 'guide/elements', + contentPath: 'guide/elements', + }, + ], + }, + { + label: 'Plantillas', + children: [ + { + label: 'Visión general', + path: 'guide/templates', + contentPath: 'guide/templates/overview', + }, + { + label: 'Enlazar texto, propiedades y atributos dinámicos', + path: 'guide/templates/binding', + contentPath: 'guide/templates/binding', + }, + { + label: 'Agregando escuchadores de eventos', + path: 'guide/templates/event-listeners', + contentPath: 'guide/templates/event-listeners', + }, + { + label: 'Enlace bidireccional', + path: 'guide/templates/two-way-binding', + contentPath: 'guide/templates/two-way-binding', + }, + { + label: 'Flujo de control', + path: 'guide/templates/control-flow', + contentPath: 'guide/templates/control-flow', + }, + { + label: 'Pipes', + path: 'guide/templates/pipes', + contentPath: 'guide/templates/pipes', + }, + { + label: 'Proyectar contenido hijo ng-content', + path: 'guide/templates/ng-content', + contentPath: 'guide/templates/ng-content', + }, + { + label: 'Crear fragmentos de plantilla con ng-template', + path: 'guide/templates/ng-template', + contentPath: 'guide/templates/ng-template', + }, + { + label: 'Agrupando elementos con ng-container', + path: 'guide/templates/ng-container', + contentPath: 'guide/templates/ng-container', + }, + { + label: 'Variables en plantillas', + path: 'guide/templates/variables', + contentPath: 'guide/templates/variables', + }, + { + label: 'Carga diferida con @defer', + path: 'guide/templates/defer', + contentPath: 'guide/templates/defer', + }, + { + label: 'Sintaxis de expresiones', + path: 'guide/templates/expression-syntax', + contentPath: 'guide/templates/expression-syntax', + }, + { + label: 'Espacios en blanco en plantillas', + path: 'guide/templates/whitespace', + contentPath: 'guide/templates/whitespace', + }, + ], + }, + { + label: 'Directivas', + children: [ + { + label: 'Visión general', + path: 'guide/directives', + contentPath: 'guide/directives/overview', + }, + { + label: 'Directivas de atributo', + path: 'guide/directives/attribute-directives', + contentPath: 'guide/directives/attribute-directives', + }, + { + label: 'Directivas estructurales', + path: 'guide/directives/structural-directives', + contentPath: 'guide/directives/structural-directives', + }, + { + label: 'API de composición de directivas', + path: 'guide/directives/directive-composition-api', + contentPath: 'guide/directives/directive-composition-api', + }, + { + label: 'Optimizando imágenes con NgOptimizedImage', + path: 'guide/image-optimization', + contentPath: 'guide/image-optimization', + }, + ], + }, + { + label: 'Inyección de Dependencias', + status: 'updated', + children: [ + { + label: 'Visión general', + path: 'guide/di', + contentPath: 'guide/di/overview', + }, + { + label: 'Creando y usando servicios', + path: 'guide/di/creating-and-using-services', + contentPath: 'guide/di/creating-and-using-services', + }, + { + label: 'Carga diferida de servicios', + path: 'guide/di/lazy-loading-services', + contentPath: 'guide/di/lazy-loading-services', + status: 'new', + }, + { + label: 'Definiendo proveedores de dependencias', + path: 'guide/di/defining-dependency-providers', + contentPath: 'guide/di/defining-dependency-providers', + }, + { + label: 'Contexto de inyección', + path: 'guide/di/dependency-injection-context', + contentPath: 'guide/di/dependency-injection-context', + }, + { + label: 'Inyectores jerárquicos', + path: 'guide/di/hierarchical-dependency-injection', + contentPath: 'guide/di/hierarchical-dependency-injection', + }, + { + label: 'Optimizando tokens de inyección', + path: 'guide/di/lightweight-injection-tokens', + contentPath: 'guide/di/lightweight-injection-tokens', + }, + { + label: 'DI en acción', + path: 'guide/di/di-in-action', + contentPath: 'guide/di/di-in-action', + }, + { + label: 'Depuración y resolución de problemas de DI', + path: 'guide/di/debugging-and-troubleshooting-di', + contentPath: 'guide/di/debugging-and-troubleshooting-di', + status: 'new', + }, + ], + }, + { + label: 'Enrutamiento', + children: [ + { + label: 'Visión general', + path: 'guide/routing', + contentPath: 'guide/routing/overview', + }, + { + label: 'Definir rutas', + path: 'guide/routing/define-routes', + contentPath: 'guide/routing/define-routes', + }, + { + label: 'Estrategias de carga de rutas', + path: 'guide/routing/loading-strategies', + contentPath: 'guide/routing/loading-strategies', + }, + { + label: 'Mostrar rutas con outlets', + path: 'guide/routing/show-routes-with-outlets', + contentPath: 'guide/routing/show-routes-with-outlets', + }, + { + label: 'Navegar a rutas', + path: 'guide/routing/navigate-to-routes', + contentPath: 'guide/routing/navigate-to-routes', + }, + { + label: 'Leer estado de ruta', + path: 'guide/routing/read-route-state', + contentPath: 'guide/routing/read-route-state', + }, + { + label: 'Redirigir rutas', + path: 'guide/routing/redirecting-routes', + contentPath: 'guide/routing/redirecting-routes', + }, + { + label: 'Controlar acceso a rutas con guards', + path: 'guide/routing/route-guards', + contentPath: 'guide/routing/route-guards', + }, + { + label: 'Resolvers de datos de ruta', + path: 'guide/routing/data-resolvers', + contentPath: 'guide/routing/data-resolvers', + }, + { + label: 'Ciclo de vida y eventos', + path: 'guide/routing/lifecycle-and-events', + contentPath: 'guide/routing/lifecycle-and-events', + }, + { + label: 'Pruebas de enrutamiento y navegación', + path: 'guide/routing/testing', + contentPath: 'guide/routing/testing', + }, + { + label: 'Otras tareas de routing', + path: 'guide/routing/common-router-tasks', + contentPath: 'guide/routing/common-router-tasks', + }, + { + label: 'Creando coincidencias de ruta personalizadas', + path: 'guide/routing/routing-with-urlmatcher', + contentPath: 'guide/routing/routing-with-urlmatcher', + }, + { + label: 'Estrategias de renderizado', + path: 'guide/routing/rendering-strategies', + contentPath: 'guide/routing/rendering-strategies', + }, + { + label: 'Personalizar comportamiento de ruta', + path: 'guide/routing/customizing-route-behavior', + contentPath: 'guide/routing/customizing-route-behavior', + }, + { + label: 'Referencia del Router', + path: 'guide/routing/router-reference', + contentPath: 'guide/routing/router-reference', + }, + { + label: 'Animaciones de transición de ruta', + path: 'guide/routing/route-transition-animations', + contentPath: 'guide/routing/route-transition-animations', + }, + ], + }, + { + label: 'Formularios', + status: 'updated', + preserveOtherCategoryOrder: true, + categoriesStatus: [ + { + 'Signal Forms': 'new', + }, + ], + children: [ + { + label: 'Visión general', + path: 'guide/forms', + contentPath: 'guide/forms/overview', + }, + + { + label: 'Visión general', + path: 'guide/forms/signals/overview', + contentPath: 'guide/forms/signals/overview', + category: 'Signal Forms', + }, + { + label: 'Modelos de formulario', + path: 'guide/forms/signals/models', + contentPath: 'guide/forms/signals/models', + category: 'Signal Forms', + }, + { + label: 'Diseño del modelo de formulario', + path: 'guide/forms/signals/model-design', + contentPath: 'guide/forms/signals/designing-your-form-model', + category: 'Signal Forms', + }, + { + label: 'Gestión del estado de campos', + path: 'guide/forms/signals/field-state-management', + contentPath: 'guide/forms/signals/field-state-management', + category: 'Signal Forms', + }, + { + label: 'Validación', + path: 'guide/forms/signals/validation', + contentPath: 'guide/forms/signals/validation', + category: 'Signal Forms', + }, + { + label: 'Lógica de formulario', + path: 'guide/forms/signals/form-logic', + contentPath: 'guide/forms/signals/form-logic', + category: 'Signal Forms', + }, + { + label: 'Lógica entre campos', + path: 'guide/forms/signals/cross-field-logic', + contentPath: 'guide/forms/signals/cross-field-logic', + category: 'Signal Forms', + }, + { + label: 'Envío de formulario', + path: 'guide/forms/signals/form-submission', + contentPath: 'guide/forms/signals/form-submission', + category: 'Signal Forms', + }, + { + label: 'Esquemas', + path: 'guide/forms/signals/schemas', + contentPath: 'guide/forms/signals/schemas', + category: 'Signal Forms', + }, + { + label: 'Metadatos de campo', + path: 'guide/forms/signals/field-metadata', + contentPath: 'guide/forms/signals/field-metadata', + category: 'Signal Forms', + }, + { + label: 'Operaciones asíncronas', + path: 'guide/forms/signals/async-operations', + contentPath: 'guide/forms/signals/async-operations', + category: 'Signal Forms', + }, + { + label: 'Formularios dinámicos con JSON', + path: 'guide/forms/signals/dynamic-forms-with-json', + contentPath: 'guide/forms/signals/dynamic-forms-with-json', + category: 'Signal Forms', + }, + { + label: 'Controles personalizados', + path: 'guide/forms/signals/custom-controls', + contentPath: 'guide/forms/signals/custom-controls', + category: 'Signal Forms', + }, + { + label: 'Pruebas', + path: 'guide/forms/signals/testing', + contentPath: 'guide/forms/signals/testing', + category: 'Signal Forms', + }, + { + label: 'Comparación con otros sistemas de formulario', + path: 'guide/forms/signals/comparison', + contentPath: 'guide/forms/signals/comparison', + category: 'Signal Forms', + }, + { + label: 'Migrando desde Reactive Forms', + path: 'guide/forms/signals/migration', + contentPath: 'guide/forms/signals/migration', + category: 'Signal Forms', + }, + { + label: 'Formularios reactivos', + path: 'guide/forms/reactive-forms', + contentPath: 'guide/forms/reactive-forms', + category: 'Reactive Forms', + }, + { + label: 'Formularios reactivos estrictamente tipados', + path: 'guide/forms/typed-forms', + contentPath: 'guide/forms/typed-forms', + category: 'Reactive Forms', + }, + { + label: 'Formularios basados en plantillas', + path: 'guide/forms/template-driven-forms', + contentPath: 'guide/forms/template-driven-forms', + category: 'Template driven Forms', + }, + { + label: 'Validar entrada de formularios', + path: 'guide/forms/form-validation', + contentPath: 'guide/forms/form-validation', + category: 'Reactive Forms', + }, + { + label: 'Validar entrada de formularios', + path: 'guide/forms/form-validation', + contentPath: 'guide/forms/form-validation', + category: 'Template driven Forms', + }, + { + label: 'Construir formularios dinámicos', + path: 'guide/forms/dynamic-forms', + contentPath: 'guide/forms/dynamic-forms', + category: 'Reactive Forms', + }, + ], + }, + { + label: 'Cliente HTTP', + children: [ + { + label: 'Visión general', + path: 'guide/http', + contentPath: 'guide/http/overview', + }, + { + label: 'Configurando HttpClient', + path: 'guide/http/setup', + contentPath: 'guide/http/setup', + }, + { + label: 'Realizando solicitudes HTTP', + path: 'guide/http/making-requests', + contentPath: 'guide/http/making-requests', + }, + { + label: 'Obtención reactiva de datos con httpResource', + path: 'guide/http/http-resource', + contentPath: 'guide/http/http-resource', + }, + { + label: 'Interceptando peticiones y respuestas', + path: 'guide/http/interceptors', + contentPath: 'guide/http/interceptors', + }, + { + label: 'Pruebas', + path: 'guide/http/testing', + contentPath: 'guide/http/testing', + }, + ], + }, + { + label: 'Renderización del lado del servidor e híbrida', + children: [ + { + label: 'Visión general', + path: 'guide/performance', + contentPath: 'guide/performance/overview', + }, + { + label: 'Renderización del lado del servidor e híbrida', + path: 'guide/ssr', + contentPath: 'guide/ssr', + }, + { + label: 'Hidratación', + path: 'guide/hydration', + contentPath: 'guide/hydration', + }, + { + label: 'Hidratación incremental', + path: 'guide/incremental-hydration', + contentPath: 'guide/incremental-hydration', + }, + ], + }, + { + label: 'Pruebas', + children: [ + { + label: 'Visión general', + path: 'guide/testing', + contentPath: 'guide/testing/overview', + }, + { + label: 'Fundamentos de pruebas de componentes', + path: 'guide/testing/components-basics', + contentPath: 'guide/testing/components-basics', + }, + { + label: 'Escenarios de pruebas de componentes', + path: 'guide/testing/components-scenarios', + contentPath: 'guide/testing/components-scenarios', + }, + { + label: 'Pruebas de servicios', + path: 'guide/testing/services', + contentPath: 'guide/testing/services', + }, + { + label: 'Pruebas de directivas de atributo', + path: 'guide/testing/attribute-directives', + contentPath: 'guide/testing/attribute-directives', + }, + { + label: 'Pruebas de pipes', + path: 'guide/testing/pipes', + contentPath: 'guide/testing/pipes', + }, + { + label: 'Pruebas de enrutamiento y navegación', + path: 'guide/routing/testing', + contentPath: 'guide/routing/testing', + status: 'new', + isCrossReferenced: true, + }, + { + label: 'Depuración de pruebas', + path: 'guide/testing/debugging', + contentPath: 'guide/testing/debugging', + }, + { + label: 'Cobertura de código', + path: 'guide/testing/code-coverage', + contentPath: 'guide/testing/code-coverage', + }, + { + label: 'APIs utilitarias de pruebas', + path: 'guide/testing/utility-apis', + contentPath: 'guide/testing/utility-apis', + }, + { + label: 'Visión general de component harnesses', + path: 'guide/testing/component-harnesses-overview', + contentPath: 'guide/testing/component-harnesses-overview', + }, + { + label: 'Usando component harnesses en pruebas', + path: 'guide/testing/using-component-harnesses', + contentPath: 'guide/testing/using-component-harnesses', + }, + { + label: 'Creando harnesses para tus componentes', + path: 'guide/testing/creating-component-harnesses', + contentPath: 'guide/testing/creating-component-harnesses', + }, + { + label: 'Agregar soporte de harness para entornos de pruebas adicionales', + path: 'guide/testing/component-harnesses-testing-environments', + contentPath: 'guide/testing/component-harnesses-testing-environments', + }, + { + label: 'Migrando de Karma a Vitest', + path: 'guide/testing/migrating-to-vitest', + contentPath: 'guide/testing/migrating-to-vitest', + }, + { + label: 'Pruebas con Karma y Jasmine', + path: 'guide/testing/karma', + contentPath: 'guide/testing/karma', + }, + { + label: 'Utilidades de pruebas de Zone.js', + path: 'guide/testing/zone-js-testing-utilities', + contentPath: 'guide/testing/zone-js-testing-utilities', + }, + ], + }, + { + label: 'Angular Aria', + status: 'new', + children: [ + { + label: 'Visión General', + path: 'guide/aria/overview', + contentPath: 'guide/aria/overview', + }, + { + label: 'Accordion', + path: 'guide/aria/accordion', + contentPath: 'guide/aria/accordion', + }, + { + label: 'Autocomplete', + path: 'guide/aria/autocomplete', + contentPath: 'guide/aria/autocomplete', + }, + { + label: 'Combobox', + path: 'guide/aria/combobox', + contentPath: 'guide/aria/combobox', + }, + { + label: 'Grid', + path: 'guide/aria/grid', + contentPath: 'guide/aria/grid', + }, + { + label: 'Listbox', + path: 'guide/aria/listbox', + contentPath: 'guide/aria/listbox', + }, + { + label: 'Menu', + path: 'guide/aria/menu', + contentPath: 'guide/aria/menu', + }, + { + label: 'Menubar', + path: 'guide/aria/menubar', + contentPath: 'guide/aria/menubar', + }, + { + label: 'Multiselect', + path: 'guide/aria/multiselect', + contentPath: 'guide/aria/multiselect', + }, + { + label: 'Select', + path: 'guide/aria/select', + contentPath: 'guide/aria/select', + }, + { + label: 'Tabs', + path: 'guide/aria/tabs', + contentPath: 'guide/aria/tabs', + }, + { + label: 'Toolbar', + path: 'guide/aria/toolbar', + contentPath: 'guide/aria/toolbar', + }, + { + label: 'Tree', + path: 'guide/aria/tree', + contentPath: 'guide/aria/tree', + }, + ], + }, + { + label: 'Internacionalización', + children: [ + { + label: 'Visión general', + path: 'guide/i18n', + contentPath: 'guide/i18n/overview', + }, + { + label: 'Agregar el paquete localize', + path: 'guide/i18n/add-package', + contentPath: 'guide/i18n/add-package', + }, + { + label: 'Referirse a configuraciones regionales por ID', + path: 'guide/i18n/locale-id', + contentPath: 'guide/i18n/locale-id', + }, + { + label: 'Formatear datos según la configuración regional', + path: 'guide/i18n/format-data-locale', + contentPath: 'guide/i18n/format-data-locale', + }, + { + label: 'Preparar un componente para traducción', + path: 'guide/i18n/prepare', + contentPath: 'guide/i18n/prepare', + }, + { + label: 'Trabajar con archivos de traducción', + path: 'guide/i18n/translation-files', + contentPath: 'guide/i18n/translation-files', + }, + { + label: 'Fusionar traducciones en la aplicación', + path: 'guide/i18n/merge', + contentPath: 'guide/i18n/merge', + }, + { + label: 'Desplegar múltiples configuraciones regionales', + path: 'guide/i18n/deploy', + contentPath: 'guide/i18n/deploy', + }, + { + label: 'Importar variantes globales de los datos de configuración regional', + path: 'guide/i18n/import-global-variants', + contentPath: 'guide/i18n/import-global-variants', + }, + { + label: 'Gestionar texto marcado con IDs personalizados', + path: 'guide/i18n/manage-marked-text', + contentPath: 'guide/i18n/manage-marked-text', + }, + { + label: 'Ejemplo de aplicación de Angular', + path: 'guide/i18n/example', + contentPath: 'guide/i18n/example', + }, + ], + }, + { + label: 'Animaciones', + children: [ + { + label: 'Animaciones de entrada y salida', + path: 'guide/animations', + contentPath: 'guide/animations/enter-and-leave', + }, + { + label: 'Animaciones complejas con CSS', + path: 'guide/animations/css', + contentPath: 'guide/animations/css', + }, + { + label: 'Animaciones de transición de ruta', + path: 'guide/routing/route-transition-animations', + contentPath: 'guide/routing/route-transition-animations', + isCrossReferenced: true, + }, + ], + }, + { + label: 'Drag and drop (arrastrar y soltar)', + path: 'guide/drag-drop', + contentPath: 'guide/drag-drop', + }, + ], + }, + { + label: 'Construir con IA', + children: [ + { + label: 'Empezar', + path: 'ai', + contentPath: 'ai/overview', + }, + { + label: 'Prompts para LLM y configuración de IDE con IA', + path: 'ai/develop-with-ai', + contentPath: 'ai/develop-with-ai', + }, + { + label: 'Habilidades de agente', + path: 'ai/agent-skills', + contentPath: 'ai/agent-skills', + status: 'new', + }, + { + label: 'Configuración del servidor MCP de Angular CLI', + path: 'ai/mcp', + contentPath: 'ai/mcp-server-setup', + }, + { + label: 'Tutor de IA para Angular', + path: 'ai/ai-tutor', + contentPath: 'ai/ai-tutor', + }, + { + label: 'Patrones de diseño', + path: 'ai/design-patterns', + contentPath: 'ai/design-patterns', + }, + { + label: 'WebMCP', + path: 'ai/webmcp', + contentPath: 'ai/webmcp', + status: 'new', + }, + ], + }, + { + label: 'Herramientas de Desarrollo', + children: [ + { + label: 'Angular CLI', + children: [ + { + label: 'Visión general', + path: 'tools/cli', + contentPath: 'tools/cli/overview', + }, + { + label: 'Configuración local', + path: 'tools/cli/setup-local', + contentPath: 'tools/cli/setup-local', + }, + { + label: 'Construyendo aplicaciones Angular', + path: 'tools/cli/build', + contentPath: 'tools/cli/build', + }, + { + label: 'Servir aplicaciones Angular para desarrollo', + path: 'tools/cli/serve', + contentPath: 'tools/cli/serve', + }, + { + label: 'Despliegue', + path: 'tools/cli/deployment', + contentPath: 'tools/cli/deployment', + }, + { + label: 'Pruebas End-to-End', + path: 'tools/cli/end-to-end', + contentPath: 'tools/cli/end-to-end', + }, + { + label: 'Migrando al nuevo sistema de construcción', + path: 'tools/cli/build-system-migration', + contentPath: 'tools/cli/build-system-migration', + }, + { + label: 'Entornos de construcción', + path: 'tools/cli/environments', + contentPath: 'tools/cli/environments', + }, + { + label: 'Builders de Angular CLI', + path: 'tools/cli/cli-builder', + contentPath: 'tools/cli/cli-builder', + }, + { + label: 'Generando código usando schematics', + path: 'tools/cli/schematics', + contentPath: 'tools/cli/schematics', + }, + { + label: 'Autorizando schematics', + path: 'tools/cli/schematics-authoring', + contentPath: 'tools/cli/schematics-authoring', + }, + { + label: 'Schematics para librerías', + path: 'tools/cli/schematics-for-libraries', + contentPath: 'tools/cli/schematics-for-libraries', + }, + { + label: 'Verificación de tipos de plantillas', + path: 'tools/cli/template-typecheck', + contentPath: 'tools/cli/template-typecheck', + }, + { + label: 'Compilación Ahead-of-time (AOT)', + path: 'tools/cli/aot-compiler', + contentPath: 'tools/cli/aot-compiler', + }, + { + label: 'Errores de metadata AOT', + path: 'tools/cli/aot-metadata-errors', + contentPath: 'tools/cli/aot-metadata-errors', + }, + ], + }, + { + label: 'Librerías', + children: [ + { + label: 'Visión general', + path: 'tools/libraries', + contentPath: 'tools/libraries/overview', + }, + { + label: 'Creando librerías', + path: 'tools/libraries/creating-libraries', + contentPath: 'tools/libraries/creating-libraries', + }, + { + label: 'Usando librerías', + path: 'tools/libraries/using-libraries', + contentPath: 'tools/libraries/using-libraries', + }, + { + label: 'Formato de Paquete Angular', + path: 'tools/libraries/angular-package-format', + contentPath: 'tools/libraries/angular-package-format', + }, + ], + }, + { + label: 'DevTools', + children: [ + { + label: 'Visión general', + path: 'tools/devtools', + contentPath: 'tools/devtools/overview', + }, + { + label: 'Componentes', + path: 'tools/devtools/component', + contentPath: 'tools/devtools/component', + }, + { + label: 'Perfilador', + path: 'tools/devtools/profiler', + contentPath: 'tools/devtools/profiler', + }, + { + label: 'Inyectores', + path: 'tools/devtools/injectors', + contentPath: 'tools/devtools/injectors', + }, + // TODO: create those guides + // The signal debugging docs should also be added to the signal section + // label: 'Signals', + // path: 'tools/devtools/signals', + // contentPath: 'tools/devtools/signals', + // }, + { + label: 'Árbol de rutas', + path: 'tools/devtools/router', + contentPath: 'tools/devtools/router', + }, + ], + }, + { + label: 'Servicio de Lenguaje', + path: 'tools/language-service', + contentPath: 'tools/language-service', + }, + ], + }, + { + label: 'Mejores Prácticas', + children: [ + { + label: 'Guía de Estilos', + path: 'style-guide', + contentPath: 'best-practices/style-guide', + }, + { + label: 'Seguridad', + path: 'best-practices/security', + contentPath: 'guide/security', // Have not refactored due to build issues + }, + { + label: 'Accesibilidad', + path: 'best-practices/a11y', + contentPath: 'best-practices/a11y', + }, + { + label: 'Errores no manejados en Angular', + path: 'best-practices/error-handling', + contentPath: 'best-practices/error-handling', + }, + { + label: 'Rendimiento', + preserveOtherCategoryOrder: true, + children: [ + { + label: 'Visión general', + path: 'best-practices/performance', + contentPath: 'best-practices/performance/overview', + }, + + // Loading Performance + { + label: 'Rutas con carga diferida', + path: 'best-practices/performance/lazy-loaded-routes', + contentPath: 'guide/routing/loading-strategies', + category: 'Loading Performance', + }, + { + label: 'Carga diferida con @defer', + path: 'best-practices/performance/defer', + contentPath: 'guide/templates/defer', + category: 'Loading Performance', + }, + { + label: 'Carga diferida de servicios', + path: 'best-practices/performance/lazy-loading-services', + contentPath: 'guide/di/lazy-loading-services', + category: 'Loading Performance', + }, + { + label: 'Optimizando imágenes con NgOptimizedImage', + path: 'best-practices/performance/image-optimization', + contentPath: 'guide/image-optimization', + category: 'Loading Performance', + }, + { + label: 'Renderización del lado del servidor e híbrida', + path: 'best-practices/performance/ssr', + contentPath: 'guide/ssr', + category: 'Loading Performance', + }, + + // Runtime Performance + { + label: 'Visión general', + path: 'best-practices/runtime-performance', + contentPath: 'best-practices/runtime-performance/overview', + category: 'Runtime Performance', + }, + { + label: 'Zoneless', + path: 'guide/zoneless', + contentPath: 'guide/zoneless', + category: 'Runtime Performance', + }, + { + label: 'Cómputos lentos', + path: 'best-practices/slow-computations', + contentPath: 'best-practices/runtime-performance/slow-computations', + category: 'Runtime Performance', + }, + { + label: 'Omitir sub-árboles de componentes', + path: 'best-practices/skipping-subtrees', + contentPath: 'best-practices/runtime-performance/skipping-subtrees', + category: 'Runtime Performance', + }, + { + label: 'Contaminación de Zone', + path: 'best-practices/zone-pollution', + contentPath: 'best-practices/runtime-performance/zone-pollution', + category: 'Runtime Performance', + }, + + { + label: 'Perfilado con Chrome DevTools', + path: 'best-practices/profiling-with-chrome-devtools', + contentPath: 'best-practices/runtime-performance/profiling-with-chrome-devtools', + category: 'Runtime Performance', + }, + ], + }, + { + label: 'Mantenerse actualizado', + path: 'update', + contentPath: 'best-practices/update', + }, + ], + }, + { + label: 'Eventos para Desarrolladores', + children: [ + { + label: 'Lanzamiento de Angular v22', + path: 'events/v22', + contentPath: 'events/v22', + status: 'new', + }, + { + label: 'Lanzamiento de Angular v21', + path: 'events/v21', + contentPath: 'events/v21', + }, + ], + }, + { + label: 'Ecosistema Extendido', + children: [ + { + label: 'NgModules', + path: 'guide/ngmodules/overview', + contentPath: 'guide/ngmodules/overview', + }, + { + label: 'Animaciones legacy', + children: [ + { + label: 'Visión general', + path: 'guide/legacy-animations', + contentPath: 'guide/animations/overview', + }, + { + label: 'Transiciones y Triggers', + path: 'guide/legacy-animations/transition-and-triggers', + contentPath: 'guide/animations/transition-and-triggers', + }, + { + label: 'Secuencias complejas', + path: 'guide/legacy-animations/complex-sequences', + contentPath: 'guide/animations/complex-sequences', + }, + { + label: 'Animaciones reutilizables', + path: 'guide/legacy-animations/reusable-animations', + contentPath: 'guide/animations/reusable-animations', + }, + { + label: 'Migrando a Animaciones CSS nativas', + path: 'guide/animations/migration', + contentPath: 'guide/animations/migration', + }, + ], + }, + { + label: 'Usando RxJS con Angular', + children: [ + { + label: 'Interoperabilidad con signals', + path: 'ecosystem/rxjs-interop', + contentPath: 'ecosystem/rxjs-interop/signals-interop', + }, + { + label: 'Interoperabilidad con outputs de componentes', + path: 'ecosystem/rxjs-interop/output-interop', + contentPath: 'ecosystem/rxjs-interop/output-interop', + }, + { + label: 'Cancelar suscripciones con takeUntilDestroyed', + path: 'ecosystem/rxjs-interop/take-until-destroyed', + contentPath: 'ecosystem/rxjs-interop/take-until-destroyed', + }, + ], + }, + { + label: 'Service Workers & PWAs', + children: [ + { + label: 'Visión general', + path: 'ecosystem/service-workers', + contentPath: 'ecosystem/service-workers/overview', + }, + { + label: 'Empezando', + path: 'ecosystem/service-workers/getting-started', + contentPath: 'ecosystem/service-workers/getting-started', + }, + { + label: 'Scripts de service worker personalizados', + path: 'ecosystem/service-workers/custom-service-worker-scripts', + contentPath: 'ecosystem/service-workers/custom-service-worker-scripts', + }, + { + label: 'Archivo de configuración', + path: 'ecosystem/service-workers/config', + contentPath: 'ecosystem/service-workers/config', + }, + { + label: 'Comunicación con el service worker', + path: 'ecosystem/service-workers/communications', + contentPath: 'ecosystem/service-workers/communications', + }, + { + label: 'Notificaciones push', + path: 'ecosystem/service-workers/push-notifications', + contentPath: 'ecosystem/service-workers/push-notifications', + }, + { + label: 'Devops del service worker', + path: 'ecosystem/service-workers/devops', + contentPath: 'ecosystem/service-workers/devops', + }, + { + label: 'Patrón App shell', + path: 'ecosystem/service-workers/app-shell', + contentPath: 'ecosystem/service-workers/app-shell', + }, + ], + }, + { + label: 'Web workers', + path: 'ecosystem/web-workers', + contentPath: 'ecosystem/web-workers', + }, + { + label: 'Pipeline de compilación personalizada', + path: 'ecosystem/custom-build-pipeline', + contentPath: 'ecosystem/custom-build-pipeline', + }, + { + label: 'Tailwind', + path: 'guide/tailwind', + contentPath: 'guide/tailwind', + }, + { + label: 'Angular Fire', + path: 'https://github.com/angular/angularfire#readme', + }, + { + label: 'Google Maps', + path: 'https://github.com/angular/components/tree/main/src/google-maps#readme', + }, + { + label: 'Google Pay', + path: 'https://github.com/google-pay/google-pay-button#angular', + }, + { + label: 'YouTube player', + path: 'https://github.com/angular/components/blob/main/src/youtube-player/README.md', + }, + { + label: 'Angular CDK', + path: 'https://material.angular.dev/cdk/categories', + }, + { + label: 'Angular Material', + path: 'https://material.angular.dev/', + }, + ], + }, + ...(isDevMode() + ? [ + { + label: 'Adev Dev Guide', + children: [ + { + label: 'Kitchen Sink', + path: 'kitchen-sink', + contentPath: 'kitchen-sink', + }, + ], + }, + ] + : []), +]; + +export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [ + FIRST_APP_TUTORIAL_NAV_DATA, + LEARN_ANGULAR_TUTORIAL_NAV_DATA, + DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA, + SIGNALS_TUTORIAL_NAV_DATA, + SIGNAL_FORMS_TUTORIAL_NAV_DATA, + { + path: 'tutorials', + contentPath: 'tutorials/home', + label: 'Tutoriales', + }, +]; + +export const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [ + { + label: 'Hoja de ruta', + path: 'roadmap', + contentPath: 'reference/roadmap', + }, + { + label: 'Get involved', + path: 'https://github.com/angular/angular/blob/main/CONTRIBUTING.md', + }, + { + label: 'Referencia de API', + preserveOtherCategoryOrder: true, + children: [ + { + label: 'Overview', + path: 'api', + }, + ...getApiNavigationItems(), + ], + }, + { + label: 'Referencia de CLI', + children: [ + { + label: 'Visión general', + path: 'cli', + contentPath: 'reference/cli', + }, + { + label: 'ng add', + path: 'cli/add', + }, + { + label: 'ng analytics', + children: [ + { + label: 'Overview', + path: 'cli/analytics', + }, + { + label: 'disable', + path: 'cli/analytics/disable', + }, + { + label: 'enable', + path: 'cli/analytics/enable', + }, + { + label: 'info', + path: 'cli/analytics/info', + }, + { + label: 'prompt', + path: 'cli/analytics/prompt', + }, + ], + }, + { + label: 'ng build', + path: 'cli/build', + }, + { + label: 'ng cache', + children: [ + { + label: 'Overview', + path: 'cli/cache', + }, + { + label: 'clean', + path: 'cli/cache/clean', + }, + { + label: 'disable', + path: 'cli/cache/disable', + }, + { + label: 'enable', + path: 'cli/cache/enable', + }, + { + label: 'info', + path: 'cli/cache/info', + }, + ], + }, + { + label: 'ng completion', + children: [ + { + label: 'Overview', + path: 'cli/completion', + }, + { + label: 'script', + path: 'cli/completion/script', + }, + ], + }, + { + label: 'ng config', + path: 'cli/config', + }, + { + label: 'ng deploy', + path: 'cli/deploy', + }, + { + label: 'ng e2e', + path: 'cli/e2e', + }, + { + label: 'ng extract-i18n', + path: 'cli/extract-i18n', + }, + { + label: 'ng generate', + children: [ + { + label: 'Overview', + path: 'cli/generate', + }, + { + label: 'ai-config', + path: 'cli/generate/ai-config', + }, + { + label: 'app-shell', + path: 'cli/generate/app-shell', + }, + { + label: 'application', + path: 'cli/generate/application', + }, + { + label: 'class', + path: 'cli/generate/class', + }, + { + label: 'component', + path: 'cli/generate/component', + }, + { + label: 'config', + path: 'cli/generate/config', + }, + { + label: 'directive', + path: 'cli/generate/directive', + }, + { + label: 'enum', + path: 'cli/generate/enum', + }, + { + label: 'environments', + path: 'cli/generate/environments', + }, + { + label: 'guard', + path: 'cli/generate/guard', + }, + { + label: 'interceptor', + path: 'cli/generate/interceptor', + }, + { + label: 'interface', + path: 'cli/generate/interface', + }, + { + label: 'library', + path: 'cli/generate/library', + }, + { + label: 'module', + path: 'cli/generate/module', + }, + { + label: 'pipe', + path: 'cli/generate/pipe', + }, + { + label: 'resolver', + path: 'cli/generate/resolver', + }, + { + label: 'service-worker', + path: 'cli/generate/service-worker', + }, + { + label: 'service', + path: 'cli/generate/service', + }, + { + label: 'web-worker', + path: 'cli/generate/web-worker', + }, + ], + }, + { + label: 'ng lint', + path: 'cli/lint', + }, + { + label: 'ng new', + path: 'cli/new', + }, + { + label: 'ng run', + path: 'cli/run', + }, + { + label: 'ng serve', + path: 'cli/serve', + }, + { + label: 'ng test', + path: 'cli/test', + }, + { + label: 'ng update', + path: 'cli/update', + }, + { + label: 'ng version', + path: 'cli/version', + }, + ], + }, + { + label: 'Enciclopedia de Errores', + children: [ + { + label: 'Visión general', + path: 'errors', + contentPath: 'reference/errors/overview', + }, + ...ERRORS_NAV_DATA, + ], + }, + { + label: 'Diagnósticos Extendidos', + children: [ + { + label: 'Visión general', + path: 'extended-diagnostics', + contentPath: 'reference/extended-diagnostics/overview', + }, + ...EXT_DIAGNOSTICS_NAV_DATA, + ], + }, + { + label: 'Versiones y lanzamientos', + path: 'reference/releases', + contentPath: 'reference/releases', + }, + { + label: 'Compatibilidad de versiones', + path: 'reference/versions', + contentPath: 'reference/versions', + }, + { + label: 'Update guide', + path: 'update-guide', + }, + { + label: 'Configuraciones', + children: [ + { + label: 'Estructura de archivos', + path: 'reference/configs/file-structure', + contentPath: 'reference/configs/file-structure', + }, + { + label: 'Configuración del espacio de trabajo', + path: 'reference/configs/workspace-config', + contentPath: 'reference/configs/workspace-config', + }, + { + label: 'Opciones del compilador Angular', + path: 'reference/configs/angular-compiler-options', + contentPath: 'reference/configs/angular-compiler-options', + }, + { + label: 'Dependencias npm', + path: 'reference/configs/npm-packages', + contentPath: 'reference/configs/npm-packages', + }, + ], + }, + { + label: 'Migraciones', + children: [ + { + label: 'Visión general', + path: 'reference/migrations', + contentPath: 'reference/migrations/overview', + }, + { + label: 'Standalone', + path: 'reference/migrations/standalone', + contentPath: 'reference/migrations/standalone', + }, + { + label: 'Sintaxis de flujo de control', + path: 'reference/migrations/control-flow', + contentPath: 'reference/migrations/control-flow', + }, + { + label: 'Función inject()', + path: 'reference/migrations/inject-function', + contentPath: 'reference/migrations/inject-function', + }, + { + label: 'Rutas con lazy loading', + path: 'reference/migrations/route-lazy-loading', + contentPath: 'reference/migrations/route-lazy-loading', + }, + { + label: 'Entradas de signal', + path: 'reference/migrations/signal-inputs', + contentPath: 'reference/migrations/signal-inputs', + }, + { + label: 'Salidas', + path: 'reference/migrations/outputs', + contentPath: 'reference/migrations/outputs', + }, + { + label: 'Consultas de signal', + path: 'reference/migrations/signal-queries', + contentPath: 'reference/migrations/signal-queries', + }, + { + label: 'Limpiar importaciones no utilizadas', + path: 'reference/migrations/cleanup-unused-imports', + contentPath: 'reference/migrations/cleanup-unused-imports', + }, + { + label: 'Etiquetas de auto-cierre', + path: 'reference/migrations/self-closing-tags', + contentPath: 'reference/migrations/self-closing-tags', + }, + { + label: 'NgClass a Class', + path: 'reference/migrations/ngclass-to-class', + contentPath: 'reference/migrations/ngclass-to-class', + }, + { + label: 'NgStyle a Style', + path: 'reference/migrations/ngstyle-to-style', + contentPath: 'reference/migrations/ngstyle-to-style', + }, + { + label: 'Migración del módulo de pruebas del Router', + path: 'reference/migrations/router-testing-module-migration', + contentPath: 'reference/migrations/router-testing-module-migration', + }, + { + label: 'CommonModule a Standalone', + path: 'reference/migrations/common-to-standalone', + contentPath: 'reference/migrations/common-to-standalone', + }, + ], + }, +]; + +export const FOOTER_NAVIGATION_DATA: NavigationItem[] = [ + { + label: 'Kit de prensa', + path: 'press-kit', + contentPath: 'reference/press-kit', + }, + { + label: 'Licencia', + path: 'license', + contentPath: 'reference/license', + }, +]; + +export const ALL_ITEMS = [ + ...DOCS_SUB_NAVIGATION_DATA, + ...REFERENCE_SUB_NAVIGATION_DATA, + ...FOOTER_NAVIGATION_DATA, + ...TUTORIALS_SUB_NAVIGATION_DATA, +]; + +function getApiNavigationItems(): NavigationItem[] { + const manifest = API_MANIFEST_JSON as any; // TODO(mri): Use proper type when the refactoring of #66252 gets in. + + const apiNavigationItems: NavigationItem[] = []; + + for (const packageEntry of manifest) { + const packageNavigationItem: NavigationItem = { + label: packageEntry.moduleLabel, + children: packageEntry.entries.map((api: any) => ({ + path: getApiUrl(packageEntry, api.name), + label: api.name, + category: api.category, + })), + }; + + apiNavigationItems.push(packageNavigationItem); + } + + return apiNavigationItems; +} + +function getApiUrl(packageEntry: any, apiName: string): string { + const packageName = packageEntry.normalizedModuleName + // packages like `angular_core` should be `core` + // packages like `angular_animation_browser` should be `animation/browser` + .replace('angular_', '') + .replaceAll('_', '/'); + return `api/${packageName}/${apiName}`; +} diff --git a/adev-es/src/app/routing/sub-navigation-data.en.ts b/adev-es/src/app/routing/sub-navigation-data.en.ts index a859ab2e..43f27a25 100644 --- a/adev-es/src/app/routing/sub-navigation-data.en.ts +++ b/adev-es/src/app/routing/sub-navigation-data.en.ts @@ -1,4 +1,4 @@ -/*! +/** * @license * Copyright Google LLC All Rights Reserved. * @@ -6,20 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {isDevMode} from '@angular/core'; import {NavigationItem} from '@angular/docs'; -// These 2 imports are expected to be red because they are generated a build time -import FIRST_APP_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/first-app/routes.json'; -import LEARN_ANGULAR_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/learn-angular/routes.json'; -import DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/deferrable-views/routes.json'; -import SIGNALS_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/signals/routes.json'; -import SIGNAL_FORMS_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/signal-forms/routes.json'; -import ERRORS_NAV_DATA from '../../../src/assets/content/reference/errors/routes.json'; -import EXT_DIAGNOSTICS_NAV_DATA from '../../../src/assets/content/reference/extended-diagnostics/routes.json'; - -import {getApiNavigationItems} from '../features/references/helpers/manifest.helper'; -import {DEFAULT_PAGES} from '../core/constants/pages'; +import { + DOCS_SUB_NAVIGATION_DATA, + FOOTER_NAVIGATION_DATA, + REFERENCE_SUB_NAVIGATION_DATA, + TUTORIALS_SUB_NAVIGATION_DATA, +} from './navigation-entries'; interface SubNavigationData { docs: NavigationItem[]; @@ -28,1596 +22,6 @@ interface SubNavigationData { footer: NavigationItem[]; } -const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [ - { - label: 'Introduction', - children: [ - { - label: 'What is Angular?', - path: 'overview', - contentPath: 'introduction/what-is-angular', - }, - { - label: 'Installation', - path: 'installation', - contentPath: 'introduction/installation', - }, - { - label: 'Essentials', - children: [ - { - label: 'Overview', - path: 'essentials', - contentPath: 'introduction/essentials/overview', - }, - { - label: 'Composition with components', - path: 'essentials/components', - contentPath: 'introduction/essentials/components', - }, - { - label: 'Reactivity with signals', - path: 'essentials/signals', - contentPath: 'introduction/essentials/signals', - }, - { - label: 'Dynamic interfaces with templates', - path: 'essentials/templates', - contentPath: 'introduction/essentials/templates', - }, - { - label: 'Forms with signals', - path: 'essentials/signal-forms', - contentPath: 'introduction/essentials/signal-forms', - status: 'new', - }, - { - label: 'Modular design with dependency injection', - path: 'essentials/dependency-injection', - contentPath: 'introduction/essentials/dependency-injection', - }, - { - label: 'Next Steps', - path: 'essentials/next-steps', - contentPath: 'introduction/essentials/next-steps', - }, - ], - }, - { - label: 'Start coding! 🚀', - path: 'tutorials/learn-angular', - }, - ], - }, - { - label: 'In-depth Guides', - children: [ - { - label: 'Signals', - children: [ - { - label: 'Overview', - path: 'guide/signals', - contentPath: 'guide/signals/overview', - }, - { - label: 'Dependent state with linkedSignal', - path: 'guide/signals/linked-signal', - contentPath: 'guide/signals/linked-signal', - }, - { - label: 'Async reactivity with resources', - path: 'guide/signals/resource', - contentPath: 'guide/signals/resource', - }, - ], - }, - { - label: 'Components', - children: [ - { - label: 'Anatomy of components', - path: 'guide/components', - contentPath: 'guide/components/anatomy-of-components', - }, - { - label: 'Selectors', - path: 'guide/components/selectors', - contentPath: 'guide/components/selectors', - }, - { - label: 'Styling', - path: 'guide/components/styling', - contentPath: 'guide/components/styling', - }, - { - label: 'Accepting data with input properties', - path: 'guide/components/inputs', - contentPath: 'guide/components/inputs', - }, - { - label: 'Custom events with outputs', - path: 'guide/components/outputs', - contentPath: 'guide/components/outputs', - }, - { - label: 'Content projection with ng-content', - path: 'guide/components/content-projection', - contentPath: 'guide/components/content-projection', - }, - { - label: 'Host elements', - path: 'guide/components/host-elements', - contentPath: 'guide/components/host-elements', - }, - { - label: 'Lifecycle', - path: 'guide/components/lifecycle', - contentPath: 'guide/components/lifecycle', - }, - { - label: 'Referencing component children with queries', - path: 'guide/components/queries', - contentPath: 'guide/components/queries', - }, - { - label: 'Using DOM APIs', - path: 'guide/components/dom-apis', - contentPath: 'guide/components/dom-apis', - }, - { - label: 'Inheritance', - path: 'guide/components/inheritance', - contentPath: 'guide/components/inheritance', - }, - { - label: 'Programmatically rendering components', - path: 'guide/components/programmatic-rendering', - contentPath: 'guide/components/programmatic-rendering', - }, - { - label: 'Advanced configuration', - path: 'guide/components/advanced-configuration', - contentPath: 'guide/components/advanced-configuration', - }, - { - label: 'Custom Elements', - path: 'guide/elements', - contentPath: 'guide/elements', - }, - ], - }, - { - label: 'Templates', - children: [ - { - label: 'Overview', - path: 'guide/templates', - contentPath: 'guide/templates/overview', - }, - { - label: 'Binding dynamic text, properties and attributes', - path: 'guide/templates/binding', - contentPath: 'guide/templates/binding', - }, - { - label: 'Adding event listeners', - path: 'guide/templates/event-listeners', - contentPath: 'guide/templates/event-listeners', - }, - { - label: 'Two-way binding', - path: 'guide/templates/two-way-binding', - contentPath: 'guide/templates/two-way-binding', - }, - { - label: 'Control flow', - path: 'guide/templates/control-flow', - contentPath: 'guide/templates/control-flow', - }, - { - label: 'Pipes', - path: 'guide/templates/pipes', - contentPath: 'guide/templates/pipes', - }, - { - label: 'Slotting child content with ng-content', - path: 'guide/templates/ng-content', - contentPath: 'guide/templates/ng-content', - }, - { - label: 'Create template fragments with ng-template', - path: 'guide/templates/ng-template', - contentPath: 'guide/templates/ng-template', - }, - { - label: 'Grouping elements with ng-container', - path: 'guide/templates/ng-container', - contentPath: 'guide/templates/ng-container', - }, - { - label: 'Variables in templates', - path: 'guide/templates/variables', - contentPath: 'guide/templates/variables', - }, - { - label: 'Deferred loading with @defer', - path: 'guide/templates/defer', - contentPath: 'guide/templates/defer', - }, - { - label: 'Expression syntax', - path: 'guide/templates/expression-syntax', - contentPath: 'guide/templates/expression-syntax', - }, - { - label: 'Whitespace in templates', - path: 'guide/templates/whitespace', - contentPath: 'guide/templates/whitespace', - }, - ], - }, - { - label: 'Directives', - children: [ - { - label: 'Overview', - path: 'guide/directives', - contentPath: 'guide/directives/overview', - }, - { - label: 'Attribute directives', - path: 'guide/directives/attribute-directives', - contentPath: 'guide/directives/attribute-directives', - }, - { - label: 'Structural directives', - path: 'guide/directives/structural-directives', - contentPath: 'guide/directives/structural-directives', - }, - { - label: 'Directive composition API', - path: 'guide/directives/directive-composition-api', - contentPath: 'guide/directives/directive-composition-api', - }, - { - label: 'Optimizing images with NgOptimizedImage', - path: 'guide/image-optimization', - contentPath: 'guide/image-optimization', - }, - ], - }, - { - label: 'Dependency Injection', - status: 'updated', - children: [ - { - label: 'Overview', - path: 'guide/di', - contentPath: 'guide/di/overview', - status: 'updated', - }, - { - label: 'Creating and using services', - path: 'guide/di/creating-and-using-services', - contentPath: 'guide/di/creating-and-using-services', - status: 'updated', - }, - { - label: 'Defining dependency providers', - path: 'guide/di/defining-dependency-providers', - contentPath: 'guide/di/defining-dependency-providers', - status: 'updated', - }, - { - label: 'Injection context', - path: 'guide/di/dependency-injection-context', - contentPath: 'guide/di/dependency-injection-context', - }, - { - label: 'Hierarchical injectors', - path: 'guide/di/hierarchical-dependency-injection', - contentPath: 'guide/di/hierarchical-dependency-injection', - }, - { - label: 'Optimizing injection tokens', - path: 'guide/di/lightweight-injection-tokens', - contentPath: 'guide/di/lightweight-injection-tokens', - }, - { - label: 'DI in action', - path: 'guide/di/di-in-action', - contentPath: 'guide/di/di-in-action', - }, - ], - }, - { - label: 'Routing', - status: 'updated', - children: [ - { - label: 'Overview', - path: 'guide/routing', - contentPath: 'guide/routing/overview', - }, - { - label: 'Define routes', - path: 'guide/routing/define-routes', - contentPath: 'guide/routing/define-routes', - }, - { - label: 'Show routes with Outlets', - path: 'guide/routing/show-routes-with-outlets', - contentPath: 'guide/routing/show-routes-with-outlets', - }, - { - label: 'Navigate to routes', - path: 'guide/routing/navigate-to-routes', - contentPath: 'guide/routing/navigate-to-routes', - }, - { - label: 'Read route state', - path: 'guide/routing/read-route-state', - contentPath: 'guide/routing/read-route-state', - }, - { - label: 'Redirecting routes', - path: 'guide/routing/redirecting-routes', - contentPath: 'guide/routing/redirecting-routes', - }, - { - label: 'Control route access with guards', - path: 'guide/routing/route-guards', - contentPath: 'guide/routing/route-guards', - }, - { - label: 'Route data resolvers', - path: 'guide/routing/data-resolvers', - contentPath: 'guide/routing/data-resolvers', - }, - { - label: 'Lifecycle and events', - path: 'guide/routing/lifecycle-and-events', - contentPath: 'guide/routing/lifecycle-and-events', - }, - { - label: 'Testing routing and navigation', - path: 'guide/routing/testing', - contentPath: 'guide/routing/testing', - status: 'new', - }, - { - label: 'Other routing tasks', - path: 'guide/routing/common-router-tasks', - contentPath: 'guide/routing/common-router-tasks', - }, - { - label: 'Creating custom route matches', - path: 'guide/routing/routing-with-urlmatcher', - contentPath: 'guide/routing/routing-with-urlmatcher', - }, - { - label: 'Rendering strategies', - path: 'guide/routing/rendering-strategies', - contentPath: 'guide/routing/rendering-strategies', - status: 'new', - }, - { - label: 'Customizing route behavior', - path: 'guide/routing/customizing-route-behavior', - contentPath: 'guide/routing/customizing-route-behavior', - status: 'new', - }, - { - label: 'Router reference', - path: 'guide/routing/router-reference', - contentPath: 'guide/routing/router-reference', - }, - { - label: 'Route transition animations', - path: 'guide/routing/route-transition-animations', - contentPath: 'guide/routing/route-transition-animations', - }, - ], - }, - { - label: 'Forms', - status: 'updated', - children: [ - { - label: 'Overview', - path: 'guide/forms', - contentPath: 'guide/forms/overview', - }, - { - label: 'Signal forms', - status: 'new', - children: [ - { - label: 'Overview', - path: 'guide/forms/signals/overview', - contentPath: 'guide/forms/signals/overview', - }, - { - label: 'Form models', - path: 'guide/forms/signals/models', - contentPath: 'guide/forms/signals/models', - }, - { - label: 'Field state management', - path: 'guide/forms/signals/field-state-management', - contentPath: 'guide/forms/signals/field-state-management', - }, - { - label: 'Validation', - path: 'guide/forms/signals/validation', - contentPath: 'guide/forms/signals/validation', - }, - { - label: 'Custom controls', - path: 'guide/forms/signals/custom-controls', - contentPath: 'guide/forms/signals/custom-controls', - }, - { - label: 'Comparison with other form systems', - path: 'guide/forms/signals/comparison', - contentPath: 'guide/forms/signals/comparison', - }, - ], - }, - { - label: 'Reactive forms', - path: 'guide/forms/reactive-forms', - contentPath: 'guide/forms/reactive-forms', - }, - { - label: 'Strictly typed reactive forms', - path: 'guide/forms/typed-forms', - contentPath: 'guide/forms/typed-forms', - }, - { - label: 'Template-driven forms', - path: 'guide/forms/template-driven-forms', - contentPath: 'guide/forms/template-driven-forms', - }, - { - label: 'Validate form input', - path: 'guide/forms/form-validation', - contentPath: 'guide/forms/form-validation', - }, - { - label: 'Building dynamic forms', - path: 'guide/forms/dynamic-forms', - contentPath: 'guide/forms/dynamic-forms', - }, - ], - }, - { - label: 'HTTP Client', - children: [ - { - label: 'Overview', - path: 'guide/http', - contentPath: 'guide/http/overview', - }, - { - label: 'Setting up HttpClient', - path: 'guide/http/setup', - contentPath: 'guide/http/setup', - }, - { - label: 'Making requests', - path: 'guide/http/making-requests', - contentPath: 'guide/http/making-requests', - }, - { - label: 'Reactive data fetching with httpResource', - path: 'guide/http/http-resource', - contentPath: 'guide/http/http-resource', - }, - { - label: 'Intercepting requests and responses', - path: 'guide/http/interceptors', - contentPath: 'guide/http/interceptors', - }, - { - label: 'Testing', - path: 'guide/http/testing', - contentPath: 'guide/http/testing', - }, - ], - }, - { - label: 'Server-side & hybrid-rendering', - children: [ - { - label: 'Overview', - path: 'guide/performance', - contentPath: 'guide/performance/overview', - }, - { - label: 'Server-side and hybrid-rendering', - path: 'guide/ssr', - contentPath: 'guide/ssr', - }, - { - label: 'Hydration', - path: 'guide/hydration', - contentPath: 'guide/hydration', - }, - { - label: 'Incremental Hydration', - path: 'guide/incremental-hydration', - contentPath: 'guide/incremental-hydration', - }, - ], - }, - { - label: 'Testing', - children: [ - { - label: 'Overview', - path: 'guide/testing', - contentPath: 'guide/testing/overview', - }, - { - label: 'Basics of testing components', - path: 'guide/testing/components-basics', - contentPath: 'guide/testing/components-basics', - }, - { - label: 'Component testing scenarios', - path: 'guide/testing/components-scenarios', - contentPath: 'guide/testing/components-scenarios', - }, - { - label: 'Testing services', - path: 'guide/testing/services', - contentPath: 'guide/testing/services', - }, - { - label: 'Testing attribute directives', - path: 'guide/testing/attribute-directives', - contentPath: 'guide/testing/attribute-directives', - }, - { - label: 'Testing pipes', - path: 'guide/testing/pipes', - contentPath: 'guide/testing/pipes', - }, - { - label: 'Testing routing and navigation', - path: 'guide/routing/testing', - contentPath: 'guide/routing/testing', - status: 'new', - }, - { - label: 'Debugging tests', - path: 'guide/testing/debugging', - contentPath: 'guide/testing/debugging', - }, - { - label: 'Code coverage', - path: 'guide/testing/code-coverage', - contentPath: 'guide/testing/code-coverage', - }, - { - label: 'Testing utility APIs', - path: 'guide/testing/utility-apis', - contentPath: 'guide/testing/utility-apis', - }, - { - label: 'Zone.js Testing Utilities', - path: 'guide/testing/zone-js-testing-utilities', - contentPath: 'guide/testing/zone-js-testing-utilities', - }, - { - label: 'Component harnesses overview', - path: 'guide/testing/component-harnesses-overview', - contentPath: 'guide/testing/component-harnesses-overview', - }, - { - label: 'Using component harnesses in tests', - path: 'guide/testing/using-component-harnesses', - contentPath: 'guide/testing/using-component-harnesses', - }, - { - label: 'Creating harnesses for your components', - path: 'guide/testing/creating-component-harnesses', - contentPath: 'guide/testing/creating-component-harnesses', - }, - { - label: 'Adding harness support for additional testing environments', - path: 'guide/testing/component-harnesses-testing-environments', - contentPath: 'guide/testing/component-harnesses-testing-environments', - }, - { - label: 'Migrating from Karma to Vitest', - path: 'guide/testing/migrating-to-vitest', - contentPath: 'guide/testing/migrating-to-vitest', - }, - { - label: 'Testing with Karma and Jasmine', - path: 'guide/testing/karma', - contentPath: 'guide/testing/karma', - }, - ], - }, - { - label: 'Angular Aria', - status: 'new', - children: [ - { - label: 'Overview', - path: 'guide/aria/overview', - contentPath: 'guide/aria/overview', - }, - { - label: 'Accordion', - path: 'guide/aria/accordion', - contentPath: 'guide/aria/accordion', - }, - { - label: 'Autocomplete', - path: 'guide/aria/autocomplete', - contentPath: 'guide/aria/autocomplete', - }, - { - label: 'Combobox', - path: 'guide/aria/combobox', - contentPath: 'guide/aria/combobox', - }, - { - label: 'Grid', - path: 'guide/aria/grid', - contentPath: 'guide/aria/grid', - }, - { - label: 'Listbox', - path: 'guide/aria/listbox', - contentPath: 'guide/aria/listbox', - }, - { - label: 'Menu', - path: 'guide/aria/menu', - contentPath: 'guide/aria/menu', - }, - { - label: 'Menubar', - path: 'guide/aria/menubar', - contentPath: 'guide/aria/menubar', - }, - { - label: 'Multiselect', - path: 'guide/aria/multiselect', - contentPath: 'guide/aria/multiselect', - }, - { - label: 'Select', - path: 'guide/aria/select', - contentPath: 'guide/aria/select', - }, - { - label: 'Tabs', - path: 'guide/aria/tabs', - contentPath: 'guide/aria/tabs', - }, - { - label: 'Toolbar', - path: 'guide/aria/toolbar', - contentPath: 'guide/aria/toolbar', - }, - { - label: 'Tree', - path: 'guide/aria/tree', - contentPath: 'guide/aria/tree', - }, - ], - }, - { - label: 'Internationalization', - children: [ - { - label: 'Overview', - path: 'guide/i18n', - contentPath: 'guide/i18n/overview', - }, - { - label: 'Add the localize package', - path: 'guide/i18n/add-package', - contentPath: 'guide/i18n/add-package', - }, - { - label: 'Refer to locales by ID', - path: 'guide/i18n/locale-id', - contentPath: 'guide/i18n/locale-id', - }, - { - label: 'Format data based on locale', - path: 'guide/i18n/format-data-locale', - contentPath: 'guide/i18n/format-data-locale', - }, - { - label: 'Prepare component for translation', - path: 'guide/i18n/prepare', - contentPath: 'guide/i18n/prepare', - }, - { - label: 'Work with translation files', - path: 'guide/i18n/translation-files', - contentPath: 'guide/i18n/translation-files', - }, - { - label: 'Merge translations into the app', - path: 'guide/i18n/merge', - contentPath: 'guide/i18n/merge', - }, - { - label: 'Deploy multiple locales', - path: 'guide/i18n/deploy', - contentPath: 'guide/i18n/deploy', - }, - { - label: 'Import global variants of the locale data', - path: 'guide/i18n/import-global-variants', - contentPath: 'guide/i18n/import-global-variants', - }, - { - label: 'Manage marked text with custom IDs', - path: 'guide/i18n/manage-marked-text', - contentPath: 'guide/i18n/manage-marked-text', - }, - { - label: 'Example Angular application', - path: 'guide/i18n/example', - contentPath: 'guide/i18n/example', - }, - ], - }, - { - label: 'Animations', - status: 'updated', - children: [ - { - label: 'Enter and Leave animations', - path: 'guide/animations', - contentPath: 'guide/animations/enter-and-leave', - status: 'new', - }, - { - label: 'Complex Animations with CSS', - path: 'guide/animations/css', - contentPath: 'guide/animations/css', - }, - { - label: 'Route transition animations', - path: 'guide/routing/route-transition-animations', - contentPath: 'guide/routing/route-transition-animations', - }, - ], - }, - { - label: 'Drag and drop', - path: 'guide/drag-drop', - contentPath: 'guide/drag-drop', - }, - ], - }, - { - label: 'Build with AI', - status: 'new', - children: [ - { - label: 'Get Started', - path: 'ai', - contentPath: 'ai/overview', - }, - { - label: 'LLM prompts and AI IDE setup', - path: 'ai/develop-with-ai', - contentPath: 'ai/develop-with-ai', - }, - { - label: 'Design Patterns', - path: 'ai/design-patterns', - contentPath: 'ai/design-patterns', - }, - { - label: 'Angular CLI MCP Server setup', - path: 'ai/mcp', - contentPath: 'ai/mcp-server-setup', - }, - { - label: 'Angular AI Tutor', - path: 'ai/ai-tutor', - contentPath: 'ai/ai-tutor', - }, - ], - }, - { - label: 'Developer Tools', - children: [ - { - label: 'Angular CLI', - children: [ - { - label: 'Overview', - path: 'tools/cli', - contentPath: 'tools/cli/overview', - }, - { - label: 'Local set-up', - path: 'tools/cli/setup-local', - contentPath: 'tools/cli/setup-local', - }, - { - label: 'Building Angular apps', - path: 'tools/cli/build', - contentPath: 'tools/cli/build', - }, - { - label: 'Serving Angular apps for development', - path: 'tools/cli/serve', - contentPath: 'tools/cli/serve', - }, - { - label: 'Deployment', - path: 'tools/cli/deployment', - contentPath: 'tools/cli/deployment', - }, - { - label: 'End-to-End Testing', - path: 'tools/cli/end-to-end', - contentPath: 'tools/cli/end-to-end', - }, - { - label: 'Migrating to new build system', - path: 'tools/cli/build-system-migration', - contentPath: 'tools/cli/build-system-migration', - }, - { - label: 'Build environments', - path: 'tools/cli/environments', - contentPath: 'tools/cli/environments', - }, - { - label: 'Angular CLI builders', - path: 'tools/cli/cli-builder', - contentPath: 'tools/cli/cli-builder', - }, - { - label: 'Generating code using schematics', - path: 'tools/cli/schematics', - contentPath: 'tools/cli/schematics', - }, - { - label: 'Authoring schematics', - path: 'tools/cli/schematics-authoring', - contentPath: 'tools/cli/schematics-authoring', - }, - { - label: 'Schematics for libraries', - path: 'tools/cli/schematics-for-libraries', - contentPath: 'tools/cli/schematics-for-libraries', - }, - { - label: 'Template type checking', - path: 'tools/cli/template-typecheck', - contentPath: 'tools/cli/template-typecheck', - }, - { - label: 'Ahead-of-time (AOT) compilation', - path: 'tools/cli/aot-compiler', - contentPath: 'tools/cli/aot-compiler', - }, - { - label: 'AOT metadata errors', - path: 'tools/cli/aot-metadata-errors', - contentPath: 'tools/cli/aot-metadata-errors', - }, - ], - }, - { - label: 'Libraries', - children: [ - { - label: 'Overview', - path: 'tools/libraries', - contentPath: 'tools/libraries/overview', - }, - { - label: 'Creating Libraries', - path: 'tools/libraries/creating-libraries', - contentPath: 'tools/libraries/creating-libraries', - }, - { - label: 'Using Libraries', - path: 'tools/libraries/using-libraries', - contentPath: 'tools/libraries/using-libraries', - }, - { - label: 'Angular Package Format', - path: 'tools/libraries/angular-package-format', - contentPath: 'tools/libraries/angular-package-format', - }, - ], - }, - { - label: 'DevTools', - children: [ - { - label: 'Overview', - path: 'tools/devtools', - contentPath: 'tools/devtools/overview', - }, - { - label: 'Components', - path: 'tools/devtools/component', - contentPath: 'tools/devtools/component', - }, - { - label: 'Profiler', - path: 'tools/devtools/profiler', - contentPath: 'tools/devtools/profiler', - }, - { - label: 'Injectors', - path: 'tools/devtools/injectors', - contentPath: 'tools/devtools/injectors', - }, - // TODO: create those guides - // The signal debugging docs should also be added to the signal section - // { - // label: 'Signals', - // path: 'tools/devtools/signals', - // contentPath: 'tools/devtools/signals', - // }, - // { - // label: 'Router', - // path: 'tools/devtools/router', - // contentPath: 'tools/devtools/router', - // } - ], - }, - { - label: 'Language Service', - path: 'tools/language-service', - contentPath: 'tools/language-service', - }, - ], - }, - { - label: 'Best Practices', - children: [ - { - label: 'Style Guide', - path: 'style-guide', - contentPath: 'best-practices/style-guide', - status: 'updated', - }, - { - label: 'Security', - path: 'best-practices/security', - contentPath: 'guide/security', // Have not refactored due to build issues - }, - { - label: 'Accessibility', - path: 'best-practices/a11y', - contentPath: 'best-practices/a11y', - }, - { - label: 'Unhandled errors in Angular', - path: 'best-practices/error-handling', - contentPath: 'best-practices/error-handling', - }, - { - label: 'Performance', - children: [ - { - label: 'Overview', - path: 'best-practices/runtime-performance', - contentPath: 'best-practices/runtime-performance/overview', - }, - { - label: 'Zone pollution', - path: 'best-practices/zone-pollution', - contentPath: 'best-practices/runtime-performance/zone-pollution', - }, - { - label: 'Slow computations', - path: 'best-practices/slow-computations', - contentPath: 'best-practices/runtime-performance/slow-computations', - }, - { - label: 'Skipping component subtrees', - path: 'best-practices/skipping-subtrees', - contentPath: 'best-practices/runtime-performance/skipping-subtrees', - }, - { - label: 'Profiling with the Chrome DevTools', - path: 'best-practices/profiling-with-chrome-devtools', - contentPath: 'best-practices/runtime-performance/profiling-with-chrome-devtools', - }, - {label: 'Zoneless', path: 'guide/zoneless', contentPath: 'guide/zoneless'}, - ], - }, - { - label: 'Keeping up-to-date', - path: 'update', - contentPath: 'best-practices/update', - }, - ], - }, - { - label: 'Developer Events', - children: [ - { - label: 'Angular v21 Release', - path: 'events/v21', - contentPath: 'events/v21', - status: 'new', - }, - ], - }, - { - label: 'Extended Ecosystem', - children: [ - { - label: 'NgModules', - path: 'guide/ngmodules/overview', - contentPath: 'guide/ngmodules/overview', - }, - { - label: 'Legacy Animations', - children: [ - { - label: 'Overview', - path: 'guide/legacy-animations', - contentPath: 'guide/animations/overview', - }, - { - label: 'Transition and Triggers', - path: 'guide/legacy-animations/transition-and-triggers', - contentPath: 'guide/animations/transition-and-triggers', - }, - { - label: 'Complex Sequences', - path: 'guide/legacy-animations/complex-sequences', - contentPath: 'guide/animations/complex-sequences', - }, - { - label: 'Reusable Animations', - path: 'guide/legacy-animations/reusable-animations', - contentPath: 'guide/animations/reusable-animations', - }, - { - label: 'Migrating to Native CSS Animations', - path: 'guide/animations/migration', - contentPath: 'guide/animations/migration', - }, - ], - }, - { - label: 'Using RxJS with Angular', - children: [ - { - label: 'Signals interop', - path: 'ecosystem/rxjs-interop', - contentPath: 'ecosystem/rxjs-interop/signals-interop', - }, - { - label: 'Component output interop', - path: 'ecosystem/rxjs-interop/output-interop', - contentPath: 'ecosystem/rxjs-interop/output-interop', - }, - { - label: 'Unsubscribing with takeUntilDestroyed', - path: 'ecosystem/rxjs-interop/take-until-destroyed', - contentPath: 'ecosystem/rxjs-interop/take-until-destroyed', - }, - ], - }, - { - label: 'Service Workers & PWAs', - children: [ - { - label: 'Overview', - path: 'ecosystem/service-workers', - contentPath: 'ecosystem/service-workers/overview', - }, - { - label: 'Getting started', - path: 'ecosystem/service-workers/getting-started', - contentPath: 'ecosystem/service-workers/getting-started', - }, - { - label: 'Custom service worker scripts', - path: 'ecosystem/service-workers/custom-service-worker-scripts', - contentPath: 'ecosystem/service-workers/custom-service-worker-scripts', - }, - { - label: 'Configuration file', - path: 'ecosystem/service-workers/config', - contentPath: 'ecosystem/service-workers/config', - }, - { - label: 'Communicating with the service worker', - path: 'ecosystem/service-workers/communications', - contentPath: 'ecosystem/service-workers/communications', - }, - { - label: 'Push notifications', - path: 'ecosystem/service-workers/push-notifications', - contentPath: 'ecosystem/service-workers/push-notifications', - }, - { - label: 'Service worker devops', - path: 'ecosystem/service-workers/devops', - contentPath: 'ecosystem/service-workers/devops', - }, - { - label: 'App shell pattern', - path: 'ecosystem/service-workers/app-shell', - contentPath: 'ecosystem/service-workers/app-shell', - }, - ], - }, - { - label: 'Web workers', - path: 'ecosystem/web-workers', - contentPath: 'ecosystem/web-workers', - }, - { - label: 'Custom build pipeline', - path: 'ecosystem/custom-build-pipeline', - contentPath: 'ecosystem/custom-build-pipeline', - }, - { - label: 'Tailwind', - path: 'guide/tailwind', - contentPath: 'guide/tailwind', - status: 'new', - }, - { - label: 'Angular Fire', - path: 'https://github.com/angular/angularfire#readme', - }, - { - label: 'Google Maps', - path: 'https://github.com/angular/components/tree/main/src/google-maps#readme', - }, - { - label: 'Google Pay', - path: 'https://github.com/google-pay/google-pay-button#angular', - }, - { - label: 'YouTube player', - path: 'https://github.com/angular/components/blob/main/src/youtube-player/README.md', - }, - { - label: 'Angular CDK', - path: 'https://material.angular.dev/cdk/categories', - }, - { - label: 'Angular Material', - path: 'https://material.angular.dev/', - }, - ], - }, - ...(isDevMode() - ? [ - { - label: 'Adev Dev Guide', - children: [ - { - label: 'Kitchen Sink', - path: 'kitchen-sink', - contentPath: 'kitchen-sink', - }, - ], - }, - ] - : []), -]; - -export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [ - FIRST_APP_TUTORIAL_NAV_DATA, - LEARN_ANGULAR_TUTORIAL_NAV_DATA, - DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA, - SIGNALS_TUTORIAL_NAV_DATA, - SIGNAL_FORMS_TUTORIAL_NAV_DATA, - { - path: DEFAULT_PAGES.TUTORIALS, - contentPath: 'tutorials/home', - label: 'Tutorials', - }, -]; - -const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [ - { - label: 'Roadmap', - path: 'roadmap', - contentPath: 'reference/roadmap', - }, - { - label: 'Get involved', - path: 'https://github.com/angular/angular/blob/main/CONTRIBUTING.md', - }, - { - label: 'API Reference', - children: [ - { - label: 'Overview', - path: 'api', - }, - ...getApiNavigationItems(), - ], - }, - { - label: 'CLI Reference', - children: [ - { - label: 'Overview', - path: 'cli', - contentPath: 'reference/cli', - }, - { - label: 'ng add', - path: 'cli/add', - }, - { - label: 'ng analytics', - children: [ - { - label: 'Overview', - path: 'cli/analytics', - }, - { - label: 'disable', - path: 'cli/analytics/disable', - }, - { - label: 'enable', - path: 'cli/analytics/enable', - }, - { - label: 'info', - path: 'cli/analytics/info', - }, - { - label: 'prompt', - path: 'cli/analytics/prompt', - }, - ], - }, - { - label: 'ng build', - path: 'cli/build', - }, - { - label: 'ng cache', - children: [ - { - label: 'Overview', - path: 'cli/cache', - }, - { - label: 'clean', - path: 'cli/cache/clean', - }, - { - label: 'disable', - path: 'cli/cache/disable', - }, - { - label: 'enable', - path: 'cli/cache/enable', - }, - { - label: 'info', - path: 'cli/cache/info', - }, - ], - }, - { - label: 'ng completion', - children: [ - { - label: 'Overview', - path: 'cli/completion', - }, - { - label: 'script', - path: 'cli/completion/script', - }, - ], - }, - { - label: 'ng config', - path: 'cli/config', - }, - { - label: 'ng deploy', - path: 'cli/deploy', - }, - { - label: 'ng e2e', - path: 'cli/e2e', - }, - { - label: 'ng extract-i18n', - path: 'cli/extract-i18n', - }, - { - label: 'ng generate', - children: [ - { - label: 'Overview', - path: 'cli/generate', - }, - { - label: 'ai-config', - path: 'cli/generate/ai-config', - }, - { - label: 'app-shell', - path: 'cli/generate/app-shell', - }, - { - label: 'application', - path: 'cli/generate/application', - }, - { - label: 'class', - path: 'cli/generate/class', - }, - { - label: 'component', - path: 'cli/generate/component', - }, - { - label: 'config', - path: 'cli/generate/config', - }, - { - label: 'directive', - path: 'cli/generate/directive', - }, - { - label: 'enum', - path: 'cli/generate/enum', - }, - { - label: 'environments', - path: 'cli/generate/environments', - }, - { - label: 'guard', - path: 'cli/generate/guard', - }, - { - label: 'interceptor', - path: 'cli/generate/interceptor', - }, - { - label: 'interface', - path: 'cli/generate/interface', - }, - { - label: 'library', - path: 'cli/generate/library', - }, - { - label: 'module', - path: 'cli/generate/module', - }, - { - label: 'pipe', - path: 'cli/generate/pipe', - }, - { - label: 'resolver', - path: 'cli/generate/resolver', - }, - { - label: 'service-worker', - path: 'cli/generate/service-worker', - }, - { - label: 'service', - path: 'cli/generate/service', - }, - { - label: 'web-worker', - path: 'cli/generate/web-worker', - }, - ], - }, - { - label: 'ng lint', - path: 'cli/lint', - }, - { - label: 'ng new', - path: 'cli/new', - }, - { - label: 'ng run', - path: 'cli/run', - }, - { - label: 'ng serve', - path: 'cli/serve', - }, - { - label: 'ng test', - path: 'cli/test', - }, - { - label: 'ng update', - path: 'cli/update', - }, - { - label: 'ng version', - path: 'cli/version', - }, - ], - }, - { - label: 'Error Encyclopedia', - children: [ - { - label: 'Overview', - path: 'errors', - contentPath: 'reference/errors/overview', - }, - ...ERRORS_NAV_DATA, - ], - }, - { - label: 'Extended Diagnostics', - children: [ - { - label: 'Overview', - path: 'extended-diagnostics', - contentPath: 'reference/extended-diagnostics/overview', - }, - ...EXT_DIAGNOSTICS_NAV_DATA, - ], - }, - { - label: 'Versioning and releases', - path: 'reference/releases', - contentPath: 'reference/releases', - }, - { - label: 'Version compatibility', - path: 'reference/versions', - contentPath: 'reference/versions', - }, - { - label: 'Update guide', - path: 'update-guide', - }, - { - label: 'Configurations', - children: [ - { - label: 'File structure', - path: 'reference/configs/file-structure', - contentPath: 'reference/configs/file-structure', - }, - { - label: 'Workspace configuration', - path: 'reference/configs/workspace-config', - contentPath: 'reference/configs/workspace-config', - }, - { - label: 'Angular compiler options', - path: 'reference/configs/angular-compiler-options', - contentPath: 'reference/configs/angular-compiler-options', - }, - { - label: 'npm dependencies', - path: 'reference/configs/npm-packages', - contentPath: 'reference/configs/npm-packages', - }, - ], - }, - { - label: 'Migrations', - children: [ - { - label: 'Overview', - path: 'reference/migrations', - contentPath: 'reference/migrations/overview', - }, - { - label: 'Standalone', - path: 'reference/migrations/standalone', - contentPath: 'reference/migrations/standalone', - }, - { - label: 'Control Flow Syntax', - path: 'reference/migrations/control-flow', - contentPath: 'reference/migrations/control-flow', - }, - { - label: 'inject() Function', - path: 'reference/migrations/inject-function', - contentPath: 'reference/migrations/inject-function', - }, - { - label: 'Lazy-loaded routes', - path: 'reference/migrations/route-lazy-loading', - contentPath: 'reference/migrations/route-lazy-loading', - }, - { - label: 'Signal inputs', - path: 'reference/migrations/signal-inputs', - contentPath: 'reference/migrations/signal-inputs', - }, - { - label: 'Outputs', - path: 'reference/migrations/outputs', - contentPath: 'reference/migrations/outputs', - }, - { - label: 'Signal queries', - path: 'reference/migrations/signal-queries', - contentPath: 'reference/migrations/signal-queries', - }, - { - label: 'Clean up unused imports', - path: 'reference/migrations/cleanup-unused-imports', - contentPath: 'reference/migrations/cleanup-unused-imports', - }, - { - label: 'Self-closing tags', - path: 'reference/migrations/self-closing-tags', - contentPath: 'reference/migrations/self-closing-tags', - }, - { - label: 'NgClass to Class', - path: 'reference/migrations/ngclass-to-class', - contentPath: 'reference/migrations/ngclass-to-class', - status: 'new', - }, - { - label: 'NgStyle to Style', - path: 'reference/migrations/ngstyle-to-style', - contentPath: 'reference/migrations/ngstyle-to-style', - status: 'new', - }, - { - label: 'Router Testing Module Migration', - path: 'reference/migrations/router-testing-module-migration', - contentPath: 'reference/migrations/router-testing-module-migration', - status: 'new', - }, - { - label: 'CommonModule to Standalone', - path: 'reference/migrations/common-to-standalone', - contentPath: 'reference/migrations/common-to-standalone', - status: 'new', - }, - ], - }, -]; - -const FOOTER_NAVIGATION_DATA: NavigationItem[] = [ - { - label: 'Press Kit', - path: 'press-kit', - contentPath: 'reference/press-kit', - }, - { - label: 'License', - path: 'license', - contentPath: 'reference/license', - }, -]; - // Docs navigation data structure, it's used to display structure in // navigation-list component And build the routing table for content pages. export const SUB_NAVIGATION_DATA: SubNavigationData = { diff --git a/adev-es/src/app/routing/sub-navigation-data.ts b/adev-es/src/app/routing/sub-navigation-data.ts index 42ce0c6a..43f27a25 100644 --- a/adev-es/src/app/routing/sub-navigation-data.ts +++ b/adev-es/src/app/routing/sub-navigation-data.ts @@ -1,4 +1,4 @@ -/*! +/** * @license * Copyright Google LLC All Rights Reserved. * @@ -6,20 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import {isDevMode} from '@angular/core'; import {NavigationItem} from '@angular/docs'; -// These 2 imports are expected to be red because they are generated a build time -import FIRST_APP_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/first-app/routes.json'; -import LEARN_ANGULAR_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/learn-angular/routes.json'; -import DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/deferrable-views/routes.json'; -import SIGNAL_FORMS_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/signal-forms/routes.json'; -import SIGNALS_TUTORIAL_NAV_DATA from '../../../src/assets/tutorials/signals/routes.json'; -import ERRORS_NAV_DATA from '../../../src/assets/content/reference/errors/routes.json'; -import EXT_DIAGNOSTICS_NAV_DATA from '../../../src/assets/content/reference/extended-diagnostics/routes.json'; - -import {getApiNavigationItems} from '../features/references/helpers/manifest.helper'; -import {DEFAULT_PAGES} from '../core/constants/pages'; +import { + DOCS_SUB_NAVIGATION_DATA, + FOOTER_NAVIGATION_DATA, + REFERENCE_SUB_NAVIGATION_DATA, + TUTORIALS_SUB_NAVIGATION_DATA, +} from './navigation-entries'; interface SubNavigationData { docs: NavigationItem[]; @@ -28,1596 +22,6 @@ interface SubNavigationData { footer: NavigationItem[]; } -const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [ - { - label: 'Introducción', - children: [ - { - label: '¿Qué es Angular?', - path: 'overview', - contentPath: 'introduction/what-is-angular', - }, - { - label: 'Instalación', - path: 'installation', - contentPath: 'introduction/installation', - }, - { - label: 'Esenciales', - children: [ - { - label: 'Visión general', - path: 'essentials', - contentPath: 'introduction/essentials/overview', - }, - { - label: 'Composición basada en componentes', - path: 'essentials/components', - contentPath: 'introduction/essentials/components', - }, - { - label: 'Reactividad con signals', - path: 'essentials/signals', - contentPath: 'introduction/essentials/signals', - }, - { - label: 'Interfaces dinámicas con plantillas', - path: 'essentials/templates', - contentPath: 'introduction/essentials/templates', - }, - { - label: 'Formularios con signals', - path: 'essentials/signal-forms', - contentPath: 'introduction/essentials/signal-forms', - status: 'new', - }, - { - label: 'Diseño modular con inyección de dependencias', - path: 'essentials/dependency-injection', - contentPath: 'introduction/essentials/dependency-injection', - }, - { - label: 'Siguientes pasos', - path: 'essentials/next-steps', - contentPath: 'introduction/essentials/next-steps', - }, - ], - }, - { - label: '¡Comienza a programar! 🚀', - path: 'tutorials/learn-angular', - }, - ], - }, - { - label: 'Guías Detalladas', - children: [ - { - label: 'Signals', - children: [ - { - label: 'Visión general', - path: 'guide/signals', - contentPath: 'guide/signals/overview', - }, - { - label: 'Estado dependiente con linkedSignal', - path: 'guide/signals/linked-signal', - contentPath: 'guide/signals/linked-signal', - }, - { - label: 'Reactividad asíncrona con resource', - path: 'guide/signals/resource', - contentPath: 'guide/signals/resource', - }, - ], - }, - { - label: 'Componentes', - children: [ - { - label: 'Anatomía de Componentes', - path: 'guide/components', - contentPath: 'guide/components/anatomy-of-components', - }, - { - label: 'Selectores', - path: 'guide/components/selectors', - contentPath: 'guide/components/selectors', - }, - { - label: 'Estilos', - path: 'guide/components/styling', - contentPath: 'guide/components/styling', - }, - { - label: 'Aceptando datos con propiedades de input', - path: 'guide/components/inputs', - contentPath: 'guide/components/inputs', - }, - { - label: 'Eventos personalizados con outputs', - path: 'guide/components/outputs', - contentPath: 'guide/components/outputs', - }, - { - label: 'Proyección de contenido con ng-content', - path: 'guide/components/content-projection', - contentPath: 'guide/components/content-projection', - }, - { - label: 'Elementos host de componentes', - path: 'guide/components/host-elements', - contentPath: 'guide/components/host-elements', - }, - { - label: 'Ciclo de vida del componente', - path: 'guide/components/lifecycle', - contentPath: 'guide/components/lifecycle', - }, - { - label: 'Referenciando hijos de componentes con consultas', - path: 'guide/components/queries', - contentPath: 'guide/components/queries', - }, - { - label: 'Usando APIs del DOM', - path: 'guide/components/dom-apis', - contentPath: 'guide/components/dom-apis', - }, - { - label: 'Herencia', - path: 'guide/components/inheritance', - contentPath: 'guide/components/inheritance', - }, - { - label: 'Renderizado programático de componentes', - path: 'guide/components/programmatic-rendering', - contentPath: 'guide/components/programmatic-rendering', - }, - { - label: 'Configuración avanzada', - path: 'guide/components/advanced-configuration', - contentPath: 'guide/components/advanced-configuration', - }, - { - label: 'Elementos personalizados', - path: 'guide/elements', - contentPath: 'guide/elements', - }, - ], - }, - { - label: 'Plantillas', - children: [ - { - label: 'Visión general', - path: 'guide/templates', - contentPath: 'guide/templates/overview', - }, - { - label: 'Enlazar texto, propiedades y atributos dinámicos', - path: 'guide/templates/binding', - contentPath: 'guide/templates/binding', - }, - { - label: 'Agregando escuchadores de eventos', - path: 'guide/templates/event-listeners', - contentPath: 'guide/templates/event-listeners', - }, - { - label: 'Enlace bidireccional', - path: 'guide/templates/two-way-binding', - contentPath: 'guide/templates/two-way-binding', - }, - { - label: 'Flujo de control', - path: 'guide/templates/control-flow', - contentPath: 'guide/templates/control-flow', - }, - { - label: 'Pipes', - path: 'guide/templates/pipes', - contentPath: 'guide/templates/pipes', - }, - { - label: 'Proyectar contenido hijo ng-content', - path: 'guide/templates/ng-content', - contentPath: 'guide/templates/ng-content', - }, - { - label: 'Crear fragmentos de plantilla con ng-template', - path: 'guide/templates/ng-template', - contentPath: 'guide/templates/ng-template', - }, - { - label: 'Agrupando elementos con ng-container', - path: 'guide/templates/ng-container', - contentPath: 'guide/templates/ng-container', - }, - { - label: 'Variables en plantillas', - path: 'guide/templates/variables', - contentPath: 'guide/templates/variables', - }, - { - label: 'Carga diferida con @defer', - path: 'guide/templates/defer', - contentPath: 'guide/templates/defer', - }, - { - label: 'Sintaxis de expresiones', - path: 'guide/templates/expression-syntax', - contentPath: 'guide/templates/expression-syntax', - }, - { - label: 'Espacios en blanco en plantillas', - path: 'guide/templates/whitespace', - contentPath: 'guide/templates/whitespace', - }, - ], - }, - { - label: 'Directivas', - children: [ - { - label: 'Visión general', - path: 'guide/directives', - contentPath: 'guide/directives/overview', - }, - { - label: 'Directivas de atributo', - path: 'guide/directives/attribute-directives', - contentPath: 'guide/directives/attribute-directives', - }, - { - label: 'Directivas estructurales', - path: 'guide/directives/structural-directives', - contentPath: 'guide/directives/structural-directives', - }, - { - label: 'API de composición de directivas', - path: 'guide/directives/directive-composition-api', - contentPath: 'guide/directives/directive-composition-api', - }, - { - label: 'Optimizando imágenes con NgOptimizedImage', - path: 'guide/image-optimization', - contentPath: 'guide/image-optimization', - }, - ], - }, - { - label: 'Inyección de Dependencias', - status: 'updated', - children: [ - { - label: 'Visión general', - path: 'guide/di', - contentPath: 'guide/di/overview', - status: 'updated', - }, - { - label: 'Creando y usando servicios', - path: 'guide/di/creating-and-using-services', - contentPath: 'guide/di/creating-and-using-services', - status: 'updated', - }, - { - label: 'Definiendo proveedores de dependencias', - path: 'guide/di/defining-dependency-providers', - contentPath: 'guide/di/defining-dependency-providers', - status: 'updated', - }, - { - label: 'Contexto de inyección', - path: 'guide/di/dependency-injection-context', - contentPath: 'guide/di/dependency-injection-context', - }, - { - label: 'Inyectores jerárquicos', - path: 'guide/di/hierarchical-dependency-injection', - contentPath: 'guide/di/hierarchical-dependency-injection', - }, - { - label: 'Optimizando tokens de inyección', - path: 'guide/di/lightweight-injection-tokens', - contentPath: 'guide/di/lightweight-injection-tokens', - }, - { - label: 'DI en acción', - path: 'guide/di/di-in-action', - contentPath: 'guide/di/di-in-action', - }, - ], - }, - { - label: 'Enrutamiento', - status: 'updated', - children: [ - { - label: 'Visión general', - path: 'guide/routing', - contentPath: 'guide/routing/overview', - }, - { - label: 'Definir rutas', - path: 'guide/routing/define-routes', - contentPath: 'guide/routing/define-routes', - }, - { - label: 'Mostrar rutas con outlets', - path: 'guide/routing/show-routes-with-outlets', - contentPath: 'guide/routing/show-routes-with-outlets', - }, - { - label: 'Navegar a rutas', - path: 'guide/routing/navigate-to-routes', - contentPath: 'guide/routing/navigate-to-routes', - }, - { - label: 'Leer estado de ruta', - path: 'guide/routing/read-route-state', - contentPath: 'guide/routing/read-route-state', - }, - { - label: 'Redirigir rutas', - path: 'guide/routing/redirecting-routes', - contentPath: 'guide/routing/redirecting-routes', - }, - { - label: 'Controlar acceso a rutas con guards', - path: 'guide/routing/route-guards', - contentPath: 'guide/routing/route-guards', - }, - { - label: 'Resolvers de datos de ruta', - path: 'guide/routing/data-resolvers', - contentPath: 'guide/routing/data-resolvers', - }, - { - label: 'Ciclo de vida y eventos', - path: 'guide/routing/lifecycle-and-events', - contentPath: 'guide/routing/lifecycle-and-events', - }, - { - label: 'Pruebas de routing y navegación', - path: 'guide/routing/testing', - contentPath: 'guide/routing/testing', - status: 'new', - }, - { - label: 'Otras tareas de routing', - path: 'guide/routing/common-router-tasks', - contentPath: 'guide/routing/common-router-tasks', - }, - { - label: 'Creando coincidencias de ruta personalizadas', - path: 'guide/routing/routing-with-urlmatcher', - contentPath: 'guide/routing/routing-with-urlmatcher', - }, - { - label: 'Estrategias de renderizado', - path: 'guide/routing/rendering-strategies', - contentPath: 'guide/routing/rendering-strategies', - status: 'new', - }, - { - label: 'Personalizar comportamiento de ruta', - path: 'guide/routing/customizing-route-behavior', - contentPath: 'guide/routing/customizing-route-behavior', - status: 'new', - }, - { - label: 'Referencia del Router', - path: 'guide/routing/router-reference', - contentPath: 'guide/routing/router-reference', - }, - { - label: 'Animaciones de transición de ruta', - path: 'guide/routing/route-transition-animations', - contentPath: 'guide/routing/route-transition-animations', - }, - ], - }, - { - label: 'Formularios', - status: 'updated', - children: [ - { - label: 'Visión general', - path: 'guide/forms', - contentPath: 'guide/forms/overview', - }, - { - label: 'Signal forms', - status: 'new', - children: [ - { - label: 'Visión general', - path: 'guide/forms/signals/overview', - contentPath: 'guide/forms/signals/overview', - }, - { - label: 'Modelos de formulario', - path: 'guide/forms/signals/models', - contentPath: 'guide/forms/signals/models', - }, - { - label: 'Gestión del estado de campos', - path: 'guide/forms/signals/field-state-management', - contentPath: 'guide/forms/signals/field-state-management', - }, - { - label: 'Validación', - path: 'guide/forms/signals/validation', - contentPath: 'guide/forms/signals/validation', - }, - { - label: 'Controles personalizados', - path: 'guide/forms/signals/custom-controls', - contentPath: 'guide/forms/signals/custom-controls', - }, - { - label: 'Comparación con otros sistemas de formulario', - path: 'guide/forms/signals/comparison', - contentPath: 'guide/forms/signals/comparison', - }, - ], - }, - { - label: 'Formularios reactivos', - path: 'guide/forms/reactive-forms', - contentPath: 'guide/forms/reactive-forms', - }, - { - label: 'Formularios reactivos estrictamente tipados', - path: 'guide/forms/typed-forms', - contentPath: 'guide/forms/typed-forms', - }, - { - label: 'Formularios basados en plantillas', - path: 'guide/forms/template-driven-forms', - contentPath: 'guide/forms/template-driven-forms', - }, - { - label: 'Validar entrada de formularios', - path: 'guide/forms/form-validation', - contentPath: 'guide/forms/form-validation', - }, - { - label: 'Construir formularios dinámicos', - path: 'guide/forms/dynamic-forms', - contentPath: 'guide/forms/dynamic-forms', - }, - ], - }, - { - label: 'Cliente HTTP', - children: [ - { - label: 'Visión general', - path: 'guide/http', - contentPath: 'guide/http/overview', - }, - { - label: 'Configurando HttpClient', - path: 'guide/http/setup', - contentPath: 'guide/http/setup', - }, - { - label: 'Realizando solicitudes HTTP', - path: 'guide/http/making-requests', - contentPath: 'guide/http/making-requests', - }, - { - label: 'Obtención reactiva de datos con httpResource', - path: 'guide/http/http-resource', - contentPath: 'guide/http/http-resource', - }, - { - label: 'Interceptando peticiones y respuestas', - path: 'guide/http/interceptors', - contentPath: 'guide/http/interceptors', - }, - { - label: 'Pruebas', - path: 'guide/http/testing', - contentPath: 'guide/http/testing', - }, - ], - }, - { - label: 'Renderización del lado del servidor e híbrida', - children: [ - { - label: 'Visión general', - path: 'guide/performance', - contentPath: 'guide/performance/overview', - }, - { - label: 'Renderización del lado del servidor e híbrida', - path: 'guide/ssr', - contentPath: 'guide/ssr', - }, - { - label: 'Hidratación', - path: 'guide/hydration', - contentPath: 'guide/hydration', - }, - { - label: 'Hidratación incremental', - path: 'guide/incremental-hydration', - contentPath: 'guide/incremental-hydration', - }, - ], - }, - { - label: 'Pruebas', - children: [ - { - label: 'Visión general', - path: 'guide/testing', - contentPath: 'guide/testing/overview', - }, - { - label: 'Fundamentos de pruebas de componentes', - path: 'guide/testing/components-basics', - contentPath: 'guide/testing/components-basics', - }, - { - label: 'Escenarios de pruebas de componentes', - path: 'guide/testing/components-scenarios', - contentPath: 'guide/testing/components-scenarios', - }, - { - label: 'Pruebas de servicios', - path: 'guide/testing/services', - contentPath: 'guide/testing/services', - }, - { - label: 'Pruebas de directivas de atributo', - path: 'guide/testing/attribute-directives', - contentPath: 'guide/testing/attribute-directives', - }, - { - label: 'Pruebas de pipes', - path: 'guide/testing/pipes', - contentPath: 'guide/testing/pipes', - }, - { - label: 'Pruebas de enrutamiento y navegación', - path: 'guide/routing/testing', - contentPath: 'guide/routing/testing', - status: 'new', - }, - { - label: 'Depuración de pruebas', - path: 'guide/testing/debugging', - contentPath: 'guide/testing/debugging', - }, - { - label: 'Cobertura de código', - path: 'guide/testing/code-coverage', - contentPath: 'guide/testing/code-coverage', - }, - { - label: 'APIs utilitarias de pruebas', - path: 'guide/testing/utility-apis', - contentPath: 'guide/testing/utility-apis', - }, - { - label: 'Utilidades de pruebas de Zone.js', - path: 'guide/testing/zone-js-testing-utilities', - contentPath: 'guide/testing/zone-js-testing-utilities', - }, - { - label: 'Visión general de component harnesses', - path: 'guide/testing/component-harnesses-overview', - contentPath: 'guide/testing/component-harnesses-overview', - }, - { - label: 'Usando component harnesses en pruebas', - path: 'guide/testing/using-component-harnesses', - contentPath: 'guide/testing/using-component-harnesses', - }, - { - label: 'Creando harnesses para tus componentes', - path: 'guide/testing/creating-component-harnesses', - contentPath: 'guide/testing/creating-component-harnesses', - }, - { - label: 'Agregar soporte de harness para entornos de pruebas adicionales', - path: 'guide/testing/component-harnesses-testing-environments', - contentPath: 'guide/testing/component-harnesses-testing-environments', - }, - { - label: 'Migrando de Karma a Vitest', - path: 'guide/testing/migrating-to-vitest', - contentPath: 'guide/testing/migrating-to-vitest', - }, - { - label: 'Pruebas con Karma y Jasmine', - path: 'guide/testing/karma', - contentPath: 'guide/testing/karma', - }, - ], - }, - { - label: 'Angular Aria', - status: 'new', - children: [ - { - label: 'Visión General', - path: 'guide/aria/overview', - contentPath: 'guide/aria/overview', - }, - { - label: 'Accordion', - path: 'guide/aria/accordion', - contentPath: 'guide/aria/accordion', - }, - { - label: 'Autocomplete', - path: 'guide/aria/autocomplete', - contentPath: 'guide/aria/autocomplete', - }, - { - label: 'Combobox', - path: 'guide/aria/combobox', - contentPath: 'guide/aria/combobox', - }, - { - label: 'Grid', - path: 'guide/aria/grid', - contentPath: 'guide/aria/grid', - }, - { - label: 'Listbox', - path: 'guide/aria/listbox', - contentPath: 'guide/aria/listbox', - }, - { - label: 'Menu', - path: 'guide/aria/menu', - contentPath: 'guide/aria/menu', - }, - { - label: 'Menubar', - path: 'guide/aria/menubar', - contentPath: 'guide/aria/menubar', - }, - { - label: 'Multiselect', - path: 'guide/aria/multiselect', - contentPath: 'guide/aria/multiselect', - }, - { - label: 'Select', - path: 'guide/aria/select', - contentPath: 'guide/aria/select', - }, - { - label: 'Tabs', - path: 'guide/aria/tabs', - contentPath: 'guide/aria/tabs', - }, - { - label: 'Toolbar', - path: 'guide/aria/toolbar', - contentPath: 'guide/aria/toolbar', - }, - { - label: 'Tree', - path: 'guide/aria/tree', - contentPath: 'guide/aria/tree', - }, - ], - }, - { - label: 'Internacionalización', - children: [ - { - label: 'Visión general', - path: 'guide/i18n', - contentPath: 'guide/i18n/overview', - }, - { - label: 'Agregar el paquete localize', - path: 'guide/i18n/add-package', - contentPath: 'guide/i18n/add-package', - }, - { - label: 'Referirse a configuraciones regionales por ID', - path: 'guide/i18n/locale-id', - contentPath: 'guide/i18n/locale-id', - }, - { - label: 'Formatear datos según la configuración regional', - path: 'guide/i18n/format-data-locale', - contentPath: 'guide/i18n/format-data-locale', - }, - { - label: 'Preparar un componente para traducción', - path: 'guide/i18n/prepare', - contentPath: 'guide/i18n/prepare', - }, - { - label: 'Trabajar con archivos de traducción', - path: 'guide/i18n/translation-files', - contentPath: 'guide/i18n/translation-files', - }, - { - label: 'Fusionar traducciones en la aplicación', - path: 'guide/i18n/merge', - contentPath: 'guide/i18n/merge', - }, - { - label: 'Desplegar múltiples configuraciones regionales', - path: 'guide/i18n/deploy', - contentPath: 'guide/i18n/deploy', - }, - { - label: 'Importar variantes globales de los datos de configuración regional', - path: 'guide/i18n/import-global-variants', - contentPath: 'guide/i18n/import-global-variants', - }, - { - label: 'Gestionar texto marcado con IDs personalizados', - path: 'guide/i18n/manage-marked-text', - contentPath: 'guide/i18n/manage-marked-text', - }, - { - label: 'Ejemplo de aplicación de Angular', - path: 'guide/i18n/example', - contentPath: 'guide/i18n/example', - }, - ], - }, - { - label: 'Animaciones', - status: 'updated', - children: [ - { - label: 'Animaciones de entrada y salida', - path: 'guide/animations', - contentPath: 'guide/animations/enter-and-leave', - status: 'new', - }, - { - label: 'Animaciones complejas con CSS', - path: 'guide/animations/css', - contentPath: 'guide/animations/css', - }, - { - label: 'Animaciones de transición de ruta', - path: 'guide/routing/route-transition-animations', - contentPath: 'guide/routing/route-transition-animations', - }, - ], - }, - { - label: 'Drag and drop (arrastrar y soltar)', - path: 'guide/drag-drop', - contentPath: 'guide/drag-drop', - }, - ], - }, - { - label: 'Construir con IA', - status: 'new', - children: [ - { - label: 'Empezar', - path: 'ai', - contentPath: 'ai/overview', - }, - { - label: 'Prompts para LLM y configuración de IDE con IA', - path: 'ai/develop-with-ai', - contentPath: 'ai/develop-with-ai', - }, - { - label: 'Patrones de diseño', - path: 'ai/design-patterns', - contentPath: 'ai/design-patterns', - }, - { - label: 'Configuración del servidor MCP de Angular CLI', - path: 'ai/mcp', - contentPath: 'ai/mcp-server-setup', - }, - { - label: 'Tutor de IA para Angular', - path: 'ai/ai-tutor', - contentPath: 'ai/ai-tutor', - }, - ], - }, - { - label: 'Herramientas de Desarrollo', - children: [ - { - label: 'Angular CLI', - children: [ - { - label: 'Visión general', - path: 'tools/cli', - contentPath: 'tools/cli/overview', - }, - { - label: 'Configuración local', - path: 'tools/cli/setup-local', - contentPath: 'tools/cli/setup-local', - }, - { - label: 'Construyendo aplicaciones Angular', - path: 'tools/cli/build', - contentPath: 'tools/cli/build', - }, - { - label: 'Servir aplicaciones Angular para desarrollo', - path: 'tools/cli/serve', - contentPath: 'tools/cli/serve', - }, - { - label: 'Despliegue', - path: 'tools/cli/deployment', - contentPath: 'tools/cli/deployment', - }, - { - label: 'Pruebas End-to-End', - path: 'tools/cli/end-to-end', - contentPath: 'tools/cli/end-to-end', - }, - { - label: 'Migrando al nuevo sistema de construcción', - path: 'tools/cli/build-system-migration', - contentPath: 'tools/cli/build-system-migration', - }, - { - label: 'Entornos de construcción', - path: 'tools/cli/environments', - contentPath: 'tools/cli/environments', - }, - { - label: 'Builders de Angular CLI', - path: 'tools/cli/cli-builder', - contentPath: 'tools/cli/cli-builder', - }, - { - label: 'Generando código usando schematics', - path: 'tools/cli/schematics', - contentPath: 'tools/cli/schematics', - }, - { - label: 'Autorizando schematics', - path: 'tools/cli/schematics-authoring', - contentPath: 'tools/cli/schematics-authoring', - }, - { - label: 'Schematics para librerías', - path: 'tools/cli/schematics-for-libraries', - contentPath: 'tools/cli/schematics-for-libraries', - }, - { - label: 'Verificación de tipos de plantillas', - path: 'tools/cli/template-typecheck', - contentPath: 'tools/cli/template-typecheck', - }, - { - label: 'Compilación Ahead-of-time (AOT)', - path: 'tools/cli/aot-compiler', - contentPath: 'tools/cli/aot-compiler', - }, - { - label: 'Errores de metadata AOT', - path: 'tools/cli/aot-metadata-errors', - contentPath: 'tools/cli/aot-metadata-errors', - }, - ], - }, - { - label: 'Librerías', - children: [ - { - label: 'Visión general', - path: 'tools/libraries', - contentPath: 'tools/libraries/overview', - }, - { - label: 'Creando librerías', - path: 'tools/libraries/creating-libraries', - contentPath: 'tools/libraries/creating-libraries', - }, - { - label: 'Usando librerías', - path: 'tools/libraries/using-libraries', - contentPath: 'tools/libraries/using-libraries', - }, - { - label: 'Formato de Paquete Angular', - path: 'tools/libraries/angular-package-format', - contentPath: 'tools/libraries/angular-package-format', - }, - ], - }, - { - label: 'DevTools', - children: [ - { - label: 'Visión general', - path: 'tools/devtools', - contentPath: 'tools/devtools/overview', - }, - { - label: 'Componentes', - path: 'tools/devtools/component', - contentPath: 'tools/devtools/component', - }, - { - label: 'Perfilador', - path: 'tools/devtools/profiler', - contentPath: 'tools/devtools/profiler', - }, - { - label: 'Inyectores', - path: 'tools/devtools/injectors', - contentPath: 'tools/devtools/injectors', - }, - // TODO: create those guides - // The signal debugging docs should also be added to the signal section - // { - // label: 'Signals', - // path: 'tools/devtools/signals', - // contentPath: 'tools/devtools/signals', - // }, - // { - // label: 'Router', - // path: 'tools/devtools/router', - // contentPath: 'tools/devtools/router', - // } - ], - }, - { - label: 'Servicio de Lenguaje', - path: 'tools/language-service', - contentPath: 'tools/language-service', - }, - ], - }, - { - label: 'Mejores Prácticas', - children: [ - { - label: 'Guía de Estilos', - path: 'style-guide', - contentPath: 'best-practices/style-guide', - status: 'updated', - }, - { - label: 'Seguridad', - path: 'best-practices/security', - contentPath: 'guide/security', // Have not refactored due to build issues - }, - { - label: 'Accesibilidad', - path: 'best-practices/a11y', - contentPath: 'best-practices/a11y', - }, - { - label: 'Errores no manejados en Angular', - path: 'best-practices/error-handling', - contentPath: 'best-practices/error-handling', - }, - { - label: 'Rendimiento', - children: [ - { - label: 'Visión general', - path: 'best-practices/runtime-performance', - contentPath: 'best-practices/runtime-performance/overview', - }, - { - label: 'Contaminación de Zone', - path: 'best-practices/zone-pollution', - contentPath: 'best-practices/runtime-performance/zone-pollution', - }, - { - label: 'Cómputos lentos', - path: 'best-practices/slow-computations', - contentPath: 'best-practices/runtime-performance/slow-computations', - }, - { - label: 'Omitir sub-árboles de componentes', - path: 'best-practices/skipping-subtrees', - contentPath: 'best-practices/runtime-performance/skipping-subtrees', - }, - { - label: 'Perfilado con Chrome DevTools', - path: 'best-practices/profiling-with-chrome-devtools', - contentPath: 'best-practices/runtime-performance/profiling-with-chrome-devtools', - }, - {label: 'Zoneless', path: 'guide/zoneless', contentPath: 'guide/zoneless'}, - ], - }, - { - label: 'Mantenerse actualizado', - path: 'update', - contentPath: 'best-practices/update', - }, - ], - }, - { - label: 'Eventos para Desarrolladores', - children: [ - { - label: 'Lanzamiento de Angular v21', - path: 'events/v21', - contentPath: 'events/v21', - status: 'new', - }, - ], - }, - { - label: 'Ecosistema Extendido', - children: [ - { - label: 'NgModules', - path: 'guide/ngmodules/overview', - contentPath: 'guide/ngmodules/overview', - }, - { - label: 'Animaciones legacy', - children: [ - { - label: 'Visión general', - path: 'guide/legacy-animations', - contentPath: 'guide/animations/overview', - }, - { - label: 'Transiciones y Triggers', - path: 'guide/legacy-animations/transition-and-triggers', - contentPath: 'guide/animations/transition-and-triggers', - }, - { - label: 'Secuencias complejas', - path: 'guide/legacy-animations/complex-sequences', - contentPath: 'guide/animations/complex-sequences', - }, - { - label: 'Animaciones reutilizables', - path: 'guide/legacy-animations/reusable-animations', - contentPath: 'guide/animations/reusable-animations', - }, - { - label: 'Migrando a Animaciones CSS nativas', - path: 'guide/animations/migration', - contentPath: 'guide/animations/migration', - }, - ], - }, - { - label: 'Usando RxJS con Angular', - children: [ - { - label: 'Interoperabilidad con signals', - path: 'ecosystem/rxjs-interop', - contentPath: 'ecosystem/rxjs-interop/signals-interop', - }, - { - label: 'Interoperabilidad con outputs de componentes', - path: 'ecosystem/rxjs-interop/output-interop', - contentPath: 'ecosystem/rxjs-interop/output-interop', - }, - { - label: 'Cancelar suscripciones con takeUntilDestroyed', - path: 'ecosystem/rxjs-interop/take-until-destroyed', - contentPath: 'ecosystem/rxjs-interop/take-until-destroyed', - }, - ], - }, - { - label: 'Service Workers & PWAs', - children: [ - { - label: 'Visión general', - path: 'ecosystem/service-workers', - contentPath: 'ecosystem/service-workers/overview', - }, - { - label: 'Empezando', - path: 'ecosystem/service-workers/getting-started', - contentPath: 'ecosystem/service-workers/getting-started', - }, - { - label: 'Scripts de service worker personalizados', - path: 'ecosystem/service-workers/custom-service-worker-scripts', - contentPath: 'ecosystem/service-workers/custom-service-worker-scripts', - }, - { - label: 'Archivo de configuración', - path: 'ecosystem/service-workers/config', - contentPath: 'ecosystem/service-workers/config', - }, - { - label: 'Comunicación con el service worker', - path: 'ecosystem/service-workers/communications', - contentPath: 'ecosystem/service-workers/communications', - }, - { - label: 'Notificaciones push', - path: 'ecosystem/service-workers/push-notifications', - contentPath: 'ecosystem/service-workers/push-notifications', - }, - { - label: 'Devops del service worker', - path: 'ecosystem/service-workers/devops', - contentPath: 'ecosystem/service-workers/devops', - }, - { - label: 'Patrón App shell', - path: 'ecosystem/service-workers/app-shell', - contentPath: 'ecosystem/service-workers/app-shell', - }, - ], - }, - { - label: 'Web workers', - path: 'ecosystem/web-workers', - contentPath: 'ecosystem/web-workers', - }, - { - label: 'Pipeline de compilación personalizada', - path: 'ecosystem/custom-build-pipeline', - contentPath: 'ecosystem/custom-build-pipeline', - }, - { - label: 'Tailwind', - path: 'guide/tailwind', - contentPath: 'guide/tailwind', - status: 'new', - }, - { - label: 'Angular Fire', - path: 'https://github.com/angular/angularfire#readme', - }, - { - label: 'Google Maps', - path: 'https://github.com/angular/components/tree/main/src/google-maps#readme', - }, - { - label: 'Google Pay', - path: 'https://github.com/google-pay/google-pay-button#angular', - }, - { - label: 'YouTube player', - path: 'https://github.com/angular/components/blob/main/src/youtube-player/README.md', - }, - { - label: 'Angular CDK', - path: 'https://material.angular.dev/cdk/categories', - }, - { - label: 'Angular Material', - path: 'https://material.angular.dev/', - }, - ], - }, - ...(isDevMode() - ? [ - { - label: 'Guía de Desarrollo Adev', - children: [ - { - label: 'Kitchen Sink', - path: 'kitchen-sink', - contentPath: 'kitchen-sink', - }, - ], - }, - ] - : []), -]; - -export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [ - FIRST_APP_TUTORIAL_NAV_DATA, - LEARN_ANGULAR_TUTORIAL_NAV_DATA, - DEFERRABLE_VIEWS_TUTORIAL_NAV_DATA, - SIGNALS_TUTORIAL_NAV_DATA, - SIGNAL_FORMS_TUTORIAL_NAV_DATA, - { - path: DEFAULT_PAGES.TUTORIALS, - contentPath: 'tutorials/home', - label: 'Tutoriales', - }, -]; - -const REFERENCE_SUB_NAVIGATION_DATA: NavigationItem[] = [ - { - label: 'Hoja de ruta', - path: 'roadmap', - contentPath: 'reference/roadmap', - }, - { - label: 'Participa', - path: 'https://github.com/angular/angular/blob/main/CONTRIBUTING.md', - }, - { - label: 'Referencia de API', - children: [ - { - label: 'Visión general', - path: 'api', - }, - ...getApiNavigationItems(), - ], - }, - { - label: 'Referencia de CLI', - children: [ - { - label: 'Visión general', - path: 'cli', - contentPath: 'reference/cli', - }, - { - label: 'ng add', - path: 'cli/add', - }, - { - label: 'ng analytics', - children: [ - { - label: 'Visión general', - path: 'cli/analytics', - }, - { - label: 'disable', - path: 'cli/analytics/disable', - }, - { - label: 'enable', - path: 'cli/analytics/enable', - }, - { - label: 'info', - path: 'cli/analytics/info', - }, - { - label: 'prompt', - path: 'cli/analytics/prompt', - }, - ], - }, - { - label: 'ng build', - path: 'cli/build', - }, - { - label: 'ng cache', - children: [ - { - label: 'Visión general', - path: 'cli/cache', - }, - { - label: 'clean', - path: 'cli/cache/clean', - }, - { - label: 'disable', - path: 'cli/cache/disable', - }, - { - label: 'enable', - path: 'cli/cache/enable', - }, - { - label: 'info', - path: 'cli/cache/info', - }, - ], - }, - { - label: 'ng completion', - children: [ - { - label: 'Visión general', - path: 'cli/completion', - }, - { - label: 'script', - path: 'cli/completion/script', - }, - ], - }, - { - label: 'ng config', - path: 'cli/config', - }, - { - label: 'ng deploy', - path: 'cli/deploy', - }, - { - label: 'ng e2e', - path: 'cli/e2e', - }, - { - label: 'ng extract-i18n', - path: 'cli/extract-i18n', - }, - { - label: 'ng generate', - children: [ - { - label: 'Visión general', - path: 'cli/generate', - }, - { - label: 'ai-config', - path: 'cli/generate/ai-config', - }, - { - label: 'app-shell', - path: 'cli/generate/app-shell', - }, - { - label: 'application', - path: 'cli/generate/application', - }, - { - label: 'class', - path: 'cli/generate/class', - }, - { - label: 'component', - path: 'cli/generate/component', - }, - { - label: 'config', - path: 'cli/generate/config', - }, - { - label: 'directive', - path: 'cli/generate/directive', - }, - { - label: 'enum', - path: 'cli/generate/enum', - }, - { - label: 'environments', - path: 'cli/generate/environments', - }, - { - label: 'guard', - path: 'cli/generate/guard', - }, - { - label: 'interceptor', - path: 'cli/generate/interceptor', - }, - { - label: 'interface', - path: 'cli/generate/interface', - }, - { - label: 'library', - path: 'cli/generate/library', - }, - { - label: 'module', - path: 'cli/generate/module', - }, - { - label: 'pipe', - path: 'cli/generate/pipe', - }, - { - label: 'resolver', - path: 'cli/generate/resolver', - }, - { - label: 'service-worker', - path: 'cli/generate/service-worker', - }, - { - label: 'service', - path: 'cli/generate/service', - }, - { - label: 'web-worker', - path: 'cli/generate/web-worker', - }, - ], - }, - { - label: 'ng lint', - path: 'cli/lint', - }, - { - label: 'ng new', - path: 'cli/new', - }, - { - label: 'ng run', - path: 'cli/run', - }, - { - label: 'ng serve', - path: 'cli/serve', - }, - { - label: 'ng test', - path: 'cli/test', - }, - { - label: 'ng update', - path: 'cli/update', - }, - { - label: 'ng version', - path: 'cli/version', - }, - ], - }, - { - label: 'Enciclopedia de Errores', - children: [ - { - label: 'Visión general', - path: 'errors', - contentPath: 'reference/errors/overview', - }, - ...ERRORS_NAV_DATA, - ], - }, - { - label: 'Diagnósticos Extendidos', - children: [ - { - label: 'Visión general', - path: 'extended-diagnostics', - contentPath: 'reference/extended-diagnostics/overview', - }, - ...EXT_DIAGNOSTICS_NAV_DATA, - ], - }, - { - label: 'Versiones y lanzamientos', - path: 'reference/releases', - contentPath: 'reference/releases', - }, - { - label: 'Compatibilidad de versiones', - path: 'reference/versions', - contentPath: 'reference/versions', - }, - { - label: 'Guía de actualización', - path: 'update-guide', - }, - { - label: 'Configuraciones', - children: [ - { - label: 'Estructura de archivos', - path: 'reference/configs/file-structure', - contentPath: 'reference/configs/file-structure', - }, - { - label: 'Configuración del espacio de trabajo', - path: 'reference/configs/workspace-config', - contentPath: 'reference/configs/workspace-config', - }, - { - label: 'Opciones del compilador Angular', - path: 'reference/configs/angular-compiler-options', - contentPath: 'reference/configs/angular-compiler-options', - }, - { - label: 'Dependencias npm', - path: 'reference/configs/npm-packages', - contentPath: 'reference/configs/npm-packages', - }, - ], - }, - { - label: 'Migraciones', - children: [ - { - label: 'Visión general', - path: 'reference/migrations', - contentPath: 'reference/migrations/overview', - }, - { - label: 'Standalone', - path: 'reference/migrations/standalone', - contentPath: 'reference/migrations/standalone', - }, - { - label: 'Sintaxis de flujo de control', - path: 'reference/migrations/control-flow', - contentPath: 'reference/migrations/control-flow', - }, - { - label: 'Función inject()', - path: 'reference/migrations/inject-function', - contentPath: 'reference/migrations/inject-function', - }, - { - label: 'Rutas con lazy loading', - path: 'reference/migrations/route-lazy-loading', - contentPath: 'reference/migrations/route-lazy-loading', - }, - { - label: 'Entradas de signal', - path: 'reference/migrations/signal-inputs', - contentPath: 'reference/migrations/signal-inputs', - }, - { - label: 'Salidas', - path: 'reference/migrations/outputs', - contentPath: 'reference/migrations/outputs', - }, - { - label: 'Consultas de signal', - path: 'reference/migrations/signal-queries', - contentPath: 'reference/migrations/signal-queries', - }, - { - label: 'Limpiar importaciones no utilizadas', - path: 'reference/migrations/cleanup-unused-imports', - contentPath: 'reference/migrations/cleanup-unused-imports', - }, - { - label: 'Etiquetas de auto-cierre', - path: 'reference/migrations/self-closing-tags', - contentPath: 'reference/migrations/self-closing-tags', - }, - { - label: 'NgClass a Class', - path: 'reference/migrations/ngclass-to-class', - contentPath: 'reference/migrations/ngclass-to-class', - status: 'new', - }, - { - label: 'NgStyle a Style', - path: 'reference/migrations/ngstyle-to-style', - contentPath: 'reference/migrations/ngstyle-to-style', - status: 'new', - }, - { - label: 'Migración del módulo de pruebas del Router', - path: 'reference/migrations/router-testing-module-migration', - contentPath: 'reference/migrations/router-testing-module-migration', - status: 'new', - }, - { - label: 'CommonModule a Standalone', - path: 'reference/migrations/common-to-standalone', - contentPath: 'reference/migrations/common-to-standalone', - status: 'new', - }, - ], - }, -]; - -const FOOTER_NAVIGATION_DATA: NavigationItem[] = [ - { - label: 'Kit de prensa', - path: 'press-kit', - contentPath: 'reference/press-kit', - }, - { - label: 'Licencia', - path: 'license', - contentPath: 'reference/license', - }, -]; - // Docs navigation data structure, it's used to display structure in // navigation-list component And build the routing table for content pages. export const SUB_NAVIGATION_DATA: SubNavigationData = { diff --git a/adev-es/src/content/ai/agent-skills.md b/adev-es/src/content/ai/agent-skills.md new file mode 100644 index 00000000..0ee75ffa --- /dev/null +++ b/adev-es/src/content/ai/agent-skills.md @@ -0,0 +1,24 @@ +# Agent Skills + +Agent Skills are specialized, domain-specific instructions and capabilities designed for AI agents like Gemini CLI. These skills provide architectural guidance, generate idiomatic Angular code, and help scaffold new projects using modern best practices. + +By using Agent Skills, you can ensure that the AI agent you are working with has the most up-to-date information about Angular's conventions, reactivity models (like Signals), and project structure. + +## Available Skills + +The Angular team maintains a collection of official skills that are regularly updated to stay in sync with the latest framework improvements. + +| Skill | Description | +| :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **`angular-developer`** | Generates Angular code and provides architectural guidance. Useful for creating components, services, or obtaining best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling, testing, or CLI tooling. | +| **`angular-new-app`** | Creates a new Angular app using the Angular CLI. Provides important guidelines for effectively setting up and structuring a modern Angular application. | + +## Using Agent Skills + +Agent Skills are designed to be used with agentic coding tools like [Gemini CLI](https://geminicli.com/docs/cli/skills/), [Antigravity](https://antigravity.google/docs/skills) and more. Activating a skill loads the specific instructions and resources needed for that task. + +To use these skills in your own environment you may follow the instructions for your specific tool or use a community tool like [skills.sh](https://skills.sh/). + +```bash +npx skills add https://github.com/angular/skills +``` diff --git a/adev-es/src/content/ai/ai-tutor.en.md b/adev-es/src/content/ai/ai-tutor.en.md index fd3b01ae..c38c778b 100644 --- a/adev-es/src/content/ai/ai-tutor.en.md +++ b/adev-es/src/content/ai/ai-tutor.en.md @@ -97,6 +97,36 @@ If you want to learn about a specific topic out of order (e.g., jump from the ba ## **Troubleshooting** +### Setup Issues + +**"launch the Angular AI tutor" doesn't do anything?** + +Make sure you have a project open first. The tutor needs an actual Angular project to work with: + +```bash +ng new my-app +cd my-app +code . +``` + +Then ensure your MCP server is running. In VS Code, open the `.vscode/mcp.json` file and click the **"Start"** button at the top of the file. + +When you type "launch the Angular AI tutor", you should see a checkmark saying +"Reviewed .vscode/mcp.json and ran start task" and a prompt asking to +"Allow task run?" — go ahead and click Allow. + +**Still not working?** + +Try typing `#angular-cli` first to load the Angular context, then paste the tutorial URL: `https://angular.dev/ai/ai-tutor` + +**How to verify the server is running** + +Open the Command Palette (`Ctrl+Shift+P`), type "MCP: List Running Servers", and look for "angular-cli" in the list. + +--- + +### General Issues + If the tutor doesn't respond correctly or you suspect an issue with your application, here are a few things to try: 1. **Type "proceed":** This can often nudge the tutor to continue to the next step in the event it gets stuck. @@ -108,7 +138,7 @@ If the tutor doesn't respond correctly or you suspect an issue with your applica ## **Your Learning Journey: The Phased Path** -You will build your application over a four-phase journey. You can follow this path from start to finish to create a complete, fully-functional Angular application. Each module builds logically upon the last, taking you from the basics to advanced, real-world features. +You will build your application over a five-phase journey. You can follow this path from start to finish to create a complete, fully-functional Angular application. Each module builds logically upon the last, taking you from the basics to advanced, real-world features. **A Note on Automated Setup:** Some modules require a setup step, like creating interfaces or mock data. In these cases, the tutor will present you with the code and file instructions. You will be responsible for creating and modifying these files as instructed before the exercise begins. @@ -141,6 +171,13 @@ You will build your application over a four-phase journey. You can follow this p - **Module 16:** Introduction to Forms - **Module 17:** Intro to Angular Material +### **Phase 5: Signal Forms** + +- **Module 18**: **Introduction to Signal Forms** +- **Module 19**: **Submitting & Resetting** +- **Module 20**: **Validation in Signal Forms** +- **Module 21**: **Field State & Error Messages** + --- ## **A Note on AI & Feedback** diff --git a/adev-es/src/content/ai/ai-tutor.md b/adev-es/src/content/ai/ai-tutor.md index 3d1062d7..401080b4 100644 --- a/adev-es/src/content/ai/ai-tutor.md +++ b/adev-es/src/content/ai/ai-tutor.md @@ -4,7 +4,7 @@ El Tutor de IA de Angular está diseñado para guiarte interactivamente paso a p Nuestro objetivo es fomentar el pensamiento crítico y ayudarte a retener lo que aprendes. En lugar de simplemente darte código, el tutor explicará conceptos, te mostrará ejemplos y luego te dará ejercicios específicos del proyecto para que los resuelvas por tu cuenta. -## Primeros Pasos +## Primeros Pasos {#get-started} Puedes acceder al tutor de IA a través del [servidor MCP de Angular](ai/mcp). @@ -14,7 +14,7 @@ Puedes acceder al tutor de IA a través del [servidor MCP de Angular](ai/mcp). 4. Ingresa un prompt como `inicia el Tutor de IA de Angular` ![Una captura de pantalla que demuestra cómo iniciar el Tutor de IA de Angular en Gemini CLI.](assets/images/launch-ai-tutor.png 'Iniciar el Tutor de IA de Angular') -## Usando el Tutor de IA +## Usando el Tutor de IA {#using-the-ai-tutor} Cada módulo comienza con una breve explicación del concepto. ![Una captura de pantalla del Tutor de IA de Angular presentando una breve explicación del concepto.](assets/images/ai-tutor-preview-1.png 'Explicación del Tutor de IA de Angular') @@ -25,7 +25,7 @@ El tutor también proporcionará un ejercicio abierto para probar tu comprensió Finalmente, el tutor verificará tu trabajo antes de pasar al siguiente módulo. ![Una captura de pantalla del Tutor de IA de Angular verificando el trabajo del usuario.](assets/images/ai-tutor-preview-4.png 'Verificación del Tutor de IA de Angular') -## Cómo Funciona: El Ciclo de Aprendizaje +## Cómo Funciona: El Ciclo de Aprendizaje {#how-it-works-the-learning-cycle} Para cada nuevo tema, seguirás un ciclo de aprendizaje que enfatiza el pensamiento crítico para ayudarte a retener mejor lo que aprendes. @@ -37,17 +37,17 @@ Una vez que hayas tenido éxito, el tutor pasará directamente al siguiente tema --- -## **Características y Comandos** +## **Características y Comandos** {#features--commands} Tienes el control de tu experiencia de aprendizaje. Usa estas características en cualquier momento: -### **Salir y Volver** +### **Salir y Volver** {#leave-and-come-back} Siéntete libre de tomar un descanso. Tu progreso está vinculado al código de tu proyecto. Cuando regreses para una nueva sesión, el tutor analizará automáticamente tus archivos para determinar exactamente dónde lo dejaste, permitiéndote retomar sin problemas justo donde estabas. **Consejo Pro:** Recomendamos encarecidamente usar Git para guardar tu progreso. Después de completar un módulo, es una buena idea hacer commit de tus cambios (por ejemplo, `git commit -m "Complete Phase 1, Module 8"`). Esto actúa como un punto de control personal al que siempre puedes volver. -### **Ajusta tu Nivel de Experiencia** +### **Ajusta tu Nivel de Experiencia** {#adjust-your-experience-level} Puedes establecer tu nivel de experiencia en **Principiante (1-3)**, **Intermedio (4-7)**, o **Experimentado (8-10)**. Puedes cambiar esta configuración en cualquier momento durante tu sesión, y el tutor adaptará inmediatamente su estilo de enseñanza para coincidir. @@ -56,7 +56,7 @@ Puedes establecer tu nivel de experiencia en **Principiante (1-3)**, **Intermedi - "Establece mi nivel de experiencia en principiante." - "Cambia mi calificación a 8." -### **Ver el Plan de Aprendizaje Completo** +### **Ver el Plan de Aprendizaje Completo** {#see-the-full-learning-plan} ¿Quieres ver el panorama general o verificar qué tan lejos has llegado? Solo pide la tabla de contenidos. @@ -68,11 +68,11 @@ Puedes establecer tu nivel de experiencia en **Principiante (1-3)**, **Intermedi El tutor mostrará el plan de aprendizaje completo y marcará tu ubicación actual. -### **Una Nota sobre los Estilos** +### **Una Nota sobre los Estilos** {#a-note-on-styling} El tutor aplicará estilos básicos a tu aplicación para mantener las cosas con un aspecto limpio. Te animamos encarecidamente a aplicar tus propios estilos para hacer la aplicación tuya. -### **Saltar el Módulo Actual** +### **Saltar el Módulo Actual** {#skip-the-current-module} Si prefieres pasar al siguiente tema en el camino de aprendizaje, puedes pedirle al tutor que salte el ejercicio actual. @@ -83,7 +83,7 @@ Si prefieres pasar al siguiente tema en el camino de aprendizaje, puedes pedirle El tutor pedirá confirmación y luego te presentará la solución de código completa para el módulo actual e intentará aplicar automáticamente cualquier actualización requerida para asegurar que puedas continuar sin problemas con el siguiente módulo. -### **Saltar a Cualquier Tema** +### **Saltar a Cualquier Tema** {#jump-to-any-topic} Si quieres aprender sobre un tema específico fuera de orden (por ejemplo, saltar de los básicos a formularios), puedes hacerlo. El tutor proporcionará el código necesario para actualizar tu proyecto al punto de inicio correcto para el módulo seleccionado e intentará aplicar automáticamente cualquier actualización requerida. @@ -95,7 +95,35 @@ Si quieres aprender sobre un tema específico fuera de orden (por ejemplo, salta --- -## **Solución de Problemas** +## **Solución de Problemas** {#troubleshooting} + +### Problemas de configuración {#setup-issues} + +**¿"iniciar el tutor de IA de Angular" no hace nada?** + +Asegúrate de tener un proyecto abierto primero. El tutor necesita un proyecto Angular real con el que trabajar: + +```bash +ng new my-app +cd my-app +code . +``` + +Luego asegúrate de que tu servidor MCP esté ejecutándose. En VS Code, abre el archivo `.vscode/mcp.json` y haz clic en el botón **"Start"** en la parte superior del archivo. + +Cuando escribas "iniciar el tutor de IA de Angular", deberías ver una marca de verificación que dice "Reviewed .vscode/mcp.json and ran start task" y un aviso preguntando "¿Permitir ejecución de tarea?" — adelante y haz clic en Permitir. + +**¿Sigue sin funcionar?** + +Intenta escribir `#angular-cli` primero para cargar el contexto de Angular, luego pega la URL del tutorial: `https://angular.dev/ai/ai-tutor` + +**Cómo verificar que el servidor está ejecutándose** + +Abre la Paleta de Comandos (`Ctrl+Shift+P`), escribe "MCP: List Running Servers" y busca "angular-cli" en la lista. + +--- + +### Problemas generales {#general-issues} Si el tutor no responde correctamente o sospechas un problema con tu aplicación, aquí hay algunas cosas que puedes intentar: @@ -106,25 +134,25 @@ Si el tutor no responde correctamente o sospechas un problema con tu aplicación 5. **Reinicio Completo del Navegador:** Los errores a veces solo se muestran en la consola de desarrollador del navegador. Un reinicio completo puede ayudar a limpiar problemas subyacentes relacionados con la aplicación. 6. **Inicia un Nuevo Chat:** Siempre puedes iniciar un nuevo chat para eliminar el historial existente y comenzar de nuevo. El tutor leerá tus archivos para encontrar el último paso en el que estabas. -## **Tu Viaje de Aprendizaje: El Camino por Fases** +## **Tu Viaje de Aprendizaje: El Camino por Fases** {#your-learning-journey-the-phased-path} -Construirás tu aplicación a lo largo de un viaje de cuatro fases. Puedes seguir este camino de principio a fin para crear una aplicación Angular completa y totalmente funcional. Cada módulo se construye lógicamente sobre el anterior, llevándote desde los básicos hasta características avanzadas del mundo real. +Construirás tu aplicación a lo largo de un viaje de cinco fases. Puedes seguir este camino de principio a fin para crear una aplicación Angular completa y totalmente funcional. Cada módulo se construye lógicamente sobre el anterior, llevándote desde los básicos hasta características avanzadas del mundo real. **Una Nota sobre la Configuración Automatizada:** Algunos módulos requieren un paso de configuración, como crear interfaces o datos de prueba. En estos casos, el tutor te presentará el código e instrucciones de archivo. Serás responsable de crear y modificar estos archivos según las instrucciones antes de que comience el ejercicio. -### **Fase 1: Fundamentos de Angular** +### **Fase 1: Fundamentos de Angular** {#phase-1-angular-fundamentals} - **Módulo 1:** Primeros Pasos - **Módulo 2:** Texto Dinámico con Interpolación - **Módulo 3:** Escuchadores de Eventos (`(click)`) -### **Fase 2: Estado y Signals** +### **Fase 2: Estado y Signals** {#phase-2-state-and-signals} - **Módulo 4:** Gestión de Estado con Writable Signals (Parte 1: `set`) - **Módulo 5:** Gestión de Estado con Writable Signals (Parte 2: `update`) - **Módulo 6:** Computed Signals -### **Fase 3: Arquitectura de Componentes** +### **Fase 3: Arquitectura de Componentes** {#phase-3-component-architecture} - **Módulo 7:** Enlace de Plantilla (Propiedades y Atributos) - **Módulo 8:** Creando y Anidando Componentes @@ -133,7 +161,7 @@ Construirás tu aplicación a lo largo de un viaje de cuatro fases. Puedes segui - **Módulo 11:** Renderizado de Listas con `@for` - **Módulo 12:** Renderizado Condicional con `@if` -### **Fase 4: Características Avanzadas y Arquitectura** +### **Fase 4: Características Avanzadas y Arquitectura** {#phase-4-advanced-features--architecture} - **Módulo 13:** Enlace Bidireccional - **Módulo 14:** Servicios e Inyección de Dependencias (DI) @@ -141,9 +169,16 @@ Construirás tu aplicación a lo largo de un viaje de cuatro fases. Puedes segui - **Módulo 16:** Introducción a los Formularios - **Módulo 17:** Introducción a Angular Material +### **Fase 5: Signal Forms** {#phase-5-signal-forms} + +- **Módulo 18**: **Introducción a Signal Forms** +- **Módulo 19**: **Envío y Restablecimiento** +- **Módulo 20**: **Validación en Signal Forms** +- **Módulo 21**: **Estado de Campo y Mensajes de Error** + --- -## **Una Nota sobre IA y Retroalimentación** +## **Una Nota sobre IA y Retroalimentación** {#a-note-on-ai--feedback} Este tutor está impulsado por un Modelo de Lenguaje Grande (LLM). Aunque hemos trabajado arduamente para hacerlo un experto, las IAs pueden cometer errores. Si encuentras una explicación o ejemplo de código que parece incorrecto, por favor háganoslo saber. Puedes corregir al tutor, y este usará tu retroalimentación para ajustar su respuesta. diff --git a/adev-es/src/content/ai/design-patterns.en.md b/adev-es/src/content/ai/design-patterns.en.md index cdcd7530..8a4ce4d6 100644 --- a/adev-es/src/content/ai/design-patterns.en.md +++ b/adev-es/src/content/ai/design-patterns.en.md @@ -27,11 +27,14 @@ storyResource = resource({ loader: ({params}): Promise => { // The params value is the current value of the storyInput signal const url = this.endpoint(); - return runFlow({ url, input: { - userInput: params, - sessionId: this.storyService.sessionId() // Read from another signal - }}); - } + return runFlow({ + url, + input: { + userInput: params, + sessionId: this.storyService.sessionId(), // Read from another signal + }, + }); + }, }); ``` @@ -56,7 +59,7 @@ storyParts = linkedSignal({ const existingStoryParts = previous?.value || []; // Return a new array with the old and new parts return [...existingStoryParts, ...newStoryParts]; - } + }, }); ``` @@ -77,14 +80,14 @@ The following example demonstrates how to create a responsive UI to dynamically
    - + } @else if (imgResource.hasValue()) { - + } @else {
    -

    Failed to load image. Click to retry.

    +

    Failed to load image. Click to retry.

    } ``` @@ -101,23 +104,23 @@ characters = resource({ // exposed by the Genkit client SDK const response = streamFlow({ url: '/streamCharacters', - input: 10 + input: 10, }); (async () => { for await (const chunk of response.stream) { data.update((prev) => { if ('value' in prev) { - return { value: `${prev.value} ${chunk}` }; + return {value: `${prev.value} ${chunk}`}; } else { - return { error: chunk as unknown as Error }; + return {error: chunk as unknown as Error}; } }); } })(); return data; - } + }, }); ``` @@ -127,47 +130,48 @@ The `characters` member is updated asynchronously and can be displayed in the te @if (characters.isLoading()) {

    Loading...

    } @else if (characters.hasValue()) { -

    {{characters.value()}}

    +

    {{ characters.value() }}

    } @else { -

    {{characters.error()}}

    +

    {{ characters.error() }}

    } ``` On the server side, in `server.ts` for example, the defined endpoint sends the data to be streamed to the client. The following code uses Gemini with the Genkit framework but this technique is applicable to other APIs that support streaming responses from LLMs: ```ts -import { startFlowServer } from '@genkit-ai/express'; -import { genkit } from "genkit/beta"; -import { googleAI, gemini20Flash } from "@genkit-ai/googleai"; +import {startFlowServer} from '@genkit-ai/express'; +import {genkit} from 'genkit/beta'; +import {googleAI, gemini20Flash} from '@genkit-ai/googleai'; -const ai = genkit({ plugins: [googleAI()] }); +const ai = genkit({plugins: [googleAI()]}); -export const streamCharacters = ai.defineFlow({ +export const streamCharacters = ai.defineFlow( + { name: 'streamCharacters', inputSchema: z.number(), outputSchema: z.string(), streamSchema: z.string(), }, - async (count, { sendChunk }) => { - const { response, stream } = ai.generateStream({ - model: gemini20Flash, - config: { - temperature: 1, - }, - prompt: `Generate ${count} different RPG game characters.`, - }); - - (async () => { - for await (const chunk of stream) { - sendChunk(chunk.content[0].text!); - } - })(); - - return (await response).text; -}); + async (count, {sendChunk}) => { + const {response, stream} = ai.generateStream({ + model: gemini20Flash, + config: { + temperature: 1, + }, + prompt: `Generate ${count} different RPG game characters.`, + }); + + (async () => { + for await (const chunk of stream) { + sendChunk(chunk.content[0].text!); + } + })(); + + return (await response).text; + }, +); startFlowServer({ flows: [streamCharacters], }); - ``` diff --git a/adev-es/src/content/ai/design-patterns.md b/adev-es/src/content/ai/design-patterns.md index 864942e0..5b49c45d 100644 --- a/adev-es/src/content/ai/design-patterns.md +++ b/adev-es/src/content/ai/design-patterns.md @@ -2,7 +2,7 @@ Interactuar con APIs de IA y Modelos de Lenguaje Grandes (LLM) introduce desafíos únicos, como gestionar operaciones asíncronas, manejar datos en streaming y diseñar una experiencia de usuario responsiva para solicitudes de red potencialmente lentas o poco confiables. Los [signals](guide/signals) de Angular y la API [`resource`](guide/signals/resource) proporcionan herramientas poderosas para resolver estos problemas de manera elegante. -## Disparando solicitudes con signals +## Disparando solicitudes con signals {#triggering-requests-with-signals} Un patrón común al trabajar con prompts proporcionados por el usuario es separar la entrada en vivo del usuario del valor enviado que dispara la llamada a la API. @@ -12,7 +12,7 @@ Un patrón común al trabajar con prompts proporcionados por el usuario es separ Esta configuración asegura que la función **`loader`** del resource solo se ejecute cuando el usuario envía explícitamente su prompt, no en cada pulsación de tecla. Puedes usar parámetros de signal adicionales, como un `sessionId` o `userId` (que pueden ser útiles para crear sesiones persistentes de LLM), en el campo `loader`. De esta manera, la solicitud siempre usa los valores actuales de estos parámetros sin volver a disparar la función asíncrona definida en el campo `loader`. -Muchos SDKs de IA proporcionan métodos auxiliares para hacer llamadas a la API. Por ejemplo, la biblioteca cliente de Genkit expone un método `runFlow` para llamar flows de Genkit, que puedes llamar desde el `loader` de un resource. Para otras APIs, puedes usar el [`httpResource`](guide/signals/resource#obtención-de-datos-reactiva-con-httpresource). +Muchos SDKs de IA proporcionan métodos auxiliares para hacer llamadas a la API. Por ejemplo, la biblioteca cliente de Genkit expone un método `runFlow` para llamar flows de Genkit, que puedes llamar desde el `loader` de un resource. Para otras APIs, puedes usar el [`httpResource`](guide/signals/resource#reactive-data-fetching-with-httpresource). El siguiente ejemplo muestra un `resource` que obtiene partes de una historia generada por IA. El `loader` se dispara solo cuando el signal `storyInput` cambia. @@ -35,7 +35,7 @@ storyResource = resource({ }); ``` -## Preparando datos de LLM para plantillas +## Preparando datos de LLM para plantillas {#preparing-llm-data-for-templates} Puedes configurar APIs de LLM para que devuelvan datos estructurados. Tipar fuertemente tu `resource` para que coincida con la salida esperada del LLM proporciona mejor seguridad de tipos y autocompletado del editor. @@ -60,14 +60,14 @@ storyParts = linkedSignal({ }); ``` -## Rendimiento y experiencia de usuario +## Rendimiento y experiencia de usuario {#performance-and-user-experience} Las APIs de LLM pueden ser más lentas y más propensas a errores que las APIs convencionales, más determinísticas. Puedes usar varias características de Angular para construir una interfaz eficiente y amigable para el usuario. - **Carga Acotada:** coloca el `resource` en el componente que directamente usa los datos. Esto ayuda a limitar los ciclos de detección de cambios (especialmente en aplicaciones zoneless) y previene bloquear otras partes de tu aplicación. Si los datos necesitan ser compartidos entre múltiples componentes, proporciona el `resource` desde un servicio. - **SSR e Hidratación:** usa Server-Side Rendering (SSR) con hidratación incremental para renderizar el contenido inicial de la página rápidamente. Puedes mostrar un placeholder para el contenido generado por IA y diferir la obtención de datos hasta que el componente se hidrate en el cliente. -- **Estado de Carga:** usa el [estado](guide/signals/resource#estado-del-resource) `LOADING` del `resource` para mostrar un indicador, como un spinner, mientras la solicitud está en curso. Este estado cubre tanto cargas iniciales como recargas. -- **Manejo de Errores y Reintentos:** usa el método [**`reload()`**](guide/signals/resource#recargando) del `resource` como una forma simple para que los usuarios reintenten solicitudes fallidas, que pueden ser más prevalentes al depender de contenido generado por IA. +- **Estado de Carga:** usa el [estado](guide/signals/resource#resource-status) `LOADING` del `resource` para mostrar un indicador, como un spinner, mientras la solicitud está en curso. Este estado cubre tanto cargas iniciales como recargas. +- **Manejo de Errores y Reintentos:** usa el método [**`reload()`**](guide/signals/resource#reloading) del `resource` como una forma simple para que los usuarios reintenten solicitudes fallidas, que pueden ser más prevalentes al depender de contenido generado por IA. El siguiente ejemplo demuestra cómo crear una interfaz de usuario responsiva para mostrar dinámicamente una imagen generada por IA con funcionalidad de carga y reintento. @@ -90,7 +90,7 @@ El siguiente ejemplo demuestra cómo crear una interfaz de usuario responsiva pa ``` -## Patrones de IA en acción: streaming de respuestas de chat +## Patrones de IA en acción: streaming de respuestas de chat {#ai-patterns-in-action-streaming-chat-responses} Las interfaces a menudo muestran resultados parciales de APIs basadas en LLM de forma incremental a medida que llegan los datos de respuesta. La API de resource de Angular proporciona la capacidad de hacer streaming de respuestas para soportar este tipo de patrón. La propiedad `stream` de `resource` acepta una función asíncrona que puedes usar para aplicar actualizaciones a un valor de signal a lo largo del tiempo. El signal que se está actualizando representa los datos que se están transmitiendo en streaming. diff --git a/adev-es/src/content/ai/develop-with-ai.en.md b/adev-es/src/content/ai/develop-with-ai.en.md index 4641f615..ed10e7ff 100644 --- a/adev-es/src/content/ai/develop-with-ai.en.md +++ b/adev-es/src/content/ai/develop-with-ai.en.md @@ -18,11 +18,11 @@ Here is a set of instructions to help LLMs generate correct code that follows An ## Rules Files -Several editors, such as Firebase Studio have rules files useful for providing critical context to LLMs. +Several editors, such as Firebase Studio have rules files useful for providing critical context to LLMs. | Environment/IDE | Rules File | Installation Instructions | | :------------------- | :--------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Firebase Studio | airules.md | Configure `airules.md` | +| Firebase Studio | airules.md | Configure `airules.md` | | Copilot powered IDEs | copilot-instructions.md | Configure `.github/copilot-instructions.md` | | Cursor | cursor.md | Configure `cursorrules.md` | | JetBrains IDEs | guidelines.md | Configure `guidelines.md` | @@ -42,7 +42,7 @@ The Angular CLI includes an experimental [Model Context Protocol (MCP) server](h - llms.txt - an index file providing links to key files and resources. - llms-full.txt - a more robust compiled set of resources describing how Angular works and how to build Angular applications. -Be sure [to check out the overview page](/ai) for more information on how to integrate AI into your Angular applications. +Be sure to check out the [overview page](/ai) for more information on how to integrate AI into your Angular applications. ## Web Codegen Scorer diff --git a/adev-es/src/content/ai/develop-with-ai.md b/adev-es/src/content/ai/develop-with-ai.md index 1f7812ee..0f5a03ab 100644 --- a/adev-es/src/content/ai/develop-with-ai.md +++ b/adev-es/src/content/ai/develop-with-ai.md @@ -4,7 +4,7 @@ Generar código con modelos de lenguaje grandes (LLMs) es un área de interés e Las instrucciones avanzadas y el prompting son un estándar emergente para soportar la generación de código moderna con detalles específicos del dominio. Esta sección contiene contenido y recursos curados para apoyar una generación de código más precisa para Angular y LLMs. -## Prompts Personalizados e Instrucciones del Sistema +## Prompts Personalizados e Instrucciones del Sistema {#custom-prompts-and-system-instructions} Mejora tu experiencia generando código con LLMs usando uno de los siguientes archivos personalizados, específicos del dominio. @@ -16,7 +16,7 @@ Aquí hay un conjunto de instrucciones para ayudar a los LLMs a generar código Haz clic aquí para descargar el archivo best-practices.md. -## Archivos de Reglas +## Archivos de Reglas {#rules-files} Varios editores, como Firebase Studio tienen archivos de reglas útiles para proporcionar contexto crítico a los LLMs. @@ -29,13 +29,13 @@ Varios editores, como .instructions.md | Configurar `.instructions.md` | | Windsurf | guidelines.md | Configurar `guidelines.md` | -## Configuración del Servidor MCP de Angular CLI +## Configuración del Servidor MCP de Angular CLI {#angular-cli-mcp-server-setup} Angular CLI incluye un [servidor de Model Context Protocol (MCP)](https://modelcontextprotocol.io/) experimental que permite a los asistentes de IA en tu entorno de desarrollo interactuar con Angular CLI. [**Aprende cómo configurar el Servidor MCP de Angular CLI**](/ai/mcp) -## Proporcionando Contexto con `llms.txt` +## Proporcionando Contexto con `llms.txt` {#providing-context-with-llmstxt} `llms.txt` es un estándar propuesto para sitios web diseñado para ayudar a los LLMs a entender y procesar mejor su contenido. El equipo de Angular ha desarrollado dos versiones de este archivo para ayudar a los LLMs y herramientas que usan LLMs para generación de código a crear mejor código Angular moderno. diff --git a/adev-es/src/content/ai/mcp-server-setup.en.md b/adev-es/src/content/ai/mcp-server-setup.en.md index 148c4311..f8901dd2 100644 --- a/adev-es/src/content/ai/mcp-server-setup.en.md +++ b/adev-es/src/content/ai/mcp-server-setup.en.md @@ -1,153 +1,125 @@ -# Angular CLI MCP Server setup +# Angular CLI MCP Server -The Angular CLI includes an experimental [Model Context Protocol (MCP) server](https://modelcontextprotocol.io/) enabling AI assistants in your development environment to interact with the Angular CLI. We've included support for CLI powered code generation, adding packages, and more. +The Angular CLI includes a Model Context Protocol (MCP) server that enables AI assistants (like Cursor, Antigravity, JetBrains AI, etc.) to interact directly with the Angular CLI. It provides tools for code generation, workspace analysis, and running builds/tests. -## Available Tools - -The Angular CLI MCP server provides several tools to assist you in your development workflow. By default, the following tools are enabled: - -| Name | Description | `local-only` | `read-only` | -| :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------: | :---------: | -| `ai_tutor` | Launches an interactive AI-powered Angular tutor. Recommended to run from a new Angular project using v20 or later. [Learn more](ai/ai-tutor). | ✅ | ✅ | -| `find_examples` | Finds authoritative code examples from a curated database of official, best-practice examples, focusing on **modern, new, and recently updated** Angular features. | ✅ | ✅ | -| `get_best_practices` | Retrieves the Angular Best Practices Guide. This guide is essential for ensuring that all code adheres to modern standards, including standalone components, typed forms, and modern control flow. | ✅ | ✅ | -| `list_projects` | Lists the names of all applications and libraries defined within an Angular workspace. It reads the `angular.json` configuration file to identify the projects. | ✅ | ✅ | -| `onpush_zoneless_migration` | Analyzes Angular code and provides a step-by-step, iterative plan to migrate it to `OnPush` change detection, a prerequisite for a zoneless application. | ✅ | ✅ | -| `search_documentation` | Searches the official Angular documentation at . This tool should be used to answer any questions about Angular, such as for APIs, tutorials, and best practices. | ❌ | ✅ | - -### Experimental Tools - -Some tools are provided in experimental / preview status since they are new or not fully tested. Enable them individually with the [`--experimental-tool`](#command-options) option and use them with caution. - -| Name | Description | `local-only` | `read-only` | -| :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------: | :---------: | -| `modernize` | Performs code migrations and provides further instructions on how to modernize Angular code to align with the latest best practices and syntax. [Learn more](https://angular.dev/reference/migrations) | ✅ | ❌ | + + If your host environment supports custom Agent Skills (such as Antigravity), you can combine the Angular CLI MCP server with the official [Angular AI Skills](https://angular.dev/ai/agent-skills). While the skills provide the agent with deep instruction-level guidance and coding standards, the MCP server provides the action tools (like compiling, running tests, and analyzing workspaces) to execute those guidelines, resulting in a complete and powerful development agent. + ## Get Started -To get started, run the following command in your terminal: +To use the MCP server, you configure your host environment (IDE or CLI) to run `npx @angular/cli mcp`. -```bash -ng mcp -``` + + + Create a file named `.antigravity/mcp.json` in your project's root: -When run from an interactive terminal, this command displays instructions on how to configure a host environment to use the MCP server. The following sections provide example configurations for several popular editors and tools. + ```json + { + "mcpServers": { + "angular-cli": { + "command": "npx", + "args": ["-y", "@angular/cli", "mcp"] + } + } + } + ``` -### Cursor + -Create a file named `.cursor/mcp.json` in your project's root and add the following configuration. You can also configure it globally in `~/.cursor/mcp.json`. + + Create `.cursor/mcp.json` in the project root (or globally at `~/.cursor/mcp.json`): -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] + ```json + { + "mcpServers": { + "angular-cli": { + "command": "npx", + "args": ["-y", "@angular/cli", "mcp"] + } + } } - } -} -``` + ``` -### Firebase Studio + -Create a file named `.idx/mcp.json` in your project's root and add the following configuration: + + Create `.vscode/mcp.json`: -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] + ```json + { + "servers": { + "angular-cli": { + "command": "npx", + "args": ["-y", "@angular/cli", "mcp"] + } + } } - } -} -``` + ``` -### Gemini CLI + + -Create a file named `.gemini/settings.json` in your project's root and add the following configuration: +## Available Tools (Default) -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] - } - } -} -``` +When the MCP server is enabled, AI agents have access to the following tools: -### JetBrains IDEs +| 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. | +| `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). | +| `run_target` | Executes a configured target (e.g., build, test, lint, e2e, deploy). | +| `search_documentation` | Searches the official documentation at `https://angular.dev`. | -In JetBrains IDEs (like IntelliJ IDEA or WebStorm), after installing the JetBrains AI Assistant plugin, go to `Settings | Tools | AI Assistant | Model Context Protocol (MCP)`. Add a new server (`+`) and select `As JSON`. Then paste the following configuration: +## Common Workflows -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] - } - } -} -``` +These workflows demonstrate how AI assistants coordinate different MCP tools to automatically achieve complex developer stories. -For the most up-to-date instructions on configuring MCP servers, please refer to the JetBrains documentation: [Connect to an MCP server](https://www.jetbrains.com/help/ai-assistant/mcp.html#connect-to-an-mcp-server). +### 1. Performance Tuning: Zoneless & OnPush Migration -### VS Code +The AI agent optimizes change detection performance and migrates components to a zoneless-ready state. -In your project's root, create a file named `.vscode/mcp.json` and add the following configuration. Note the use of the `servers` property. +1. **Discover Workspace**: The AI agent calls `list_projects` to locate components, projects, and style/test configurations in the workspace. +2. **Schematic Modernization (Prerequisite)**: The AI agent runs any prerequisite signal migrations using standard `ng generate` commands (e.g., Signal Inputs, Signal Queries). +3. **Plan Migration**: The AI agent calls `onpush_zoneless_migration` with the absolute path of the directory or component file. +4. **Apply Changes**: The AI agent automatically applies the single actionable change returned by the tool to the codebase. +5. **Verify Changes**: The AI agent runs unit tests by calling `run_target` with the target parameter set to `"test"`. +6. **Repeat**: The AI agent calls `onpush_zoneless_migration` again to retrieve the next step, repeating until the tool indicates the migration is complete. -```json -{ - "servers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] - } - } -} -``` +### 2. Feature Development & TDD Loop -### Other IDEs +The AI agent automates research, implementation, and verification when developing new features. -For other IDEs, check your IDE's documentation for the proper location of the MCP configuration file (often `mcp.json`). The configuration should contain the following snippet. +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. +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`. -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] - } - } -} -``` +### 3. Developer Onboarding and Learning + +The AI agent guides the developer through Angular concepts in an interactive sandbox. + +1. **Discover Projects**: The AI agent calls `list_projects` to scan the workspace and identify the codebase structure. +2. **Launch Tutor**: The AI agent runs `ai_tutor` to load the curriculum instructions, persona, and tutoring guidelines. +3. **Follow the Curriculum**: The AI agent guides the user through the curriculum, explaining concepts and instructing them on what components to build or modify. +4. **Implement & Verify**: The AI agent helps implement the sandbox code and verifies changes using `run_target` with `"test"` or `"build"`. ## Command Options -The `mcp` command can be configured with the following options passed as arguments in your IDE's MCP configuration: +You can pass arguments to the MCP server in the `args` array of your configuration: -| Option | Type | Description | Default | -| :---------------------------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------- | :------ | -| `--read-only` | `boolean` | Only register tools that do not make changes to the project. Your editor or coding agent may still perform edits. | `false` | -| `--local-only` | `boolean` | Only register tools that do not require an internet connection. Your editor or coding agent may still send data over the network. | `false` | -| `--experimental-tool`
    `-E` | `string` | Enable an [experimental tool](#experimental-tools). Separate multiple options by spaces, e.g. `-E tool_a tool_b`. | | +- `--read-only`: Only registers tools that do not modify the project. +- `--local-only`: Only registers tools that do not require an internet connection. -For example, to run the server in read-only mode in VS Code, you would update your `mcp.json` like this: +Example for read-only mode: ```json -{ - "servers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp", "--read-only"] - } - } -} +"args": ["-y", "@angular/cli", "mcp", "--read-only"] ``` - -## Feedback and New Ideas - -The Angular team welcomes your feedback on the existing MCP capabilities and any ideas you have for new tools or features. Please share your thoughts by opening an issue on the [angular/angular GitHub repository](https://github.com/angular/angular/issues). diff --git a/adev-es/src/content/ai/mcp-server-setup.md b/adev-es/src/content/ai/mcp-server-setup.md index e6266ed9..4c284e46 100644 --- a/adev-es/src/content/ai/mcp-server-setup.md +++ b/adev-es/src/content/ai/mcp-server-setup.md @@ -1,153 +1,125 @@ # Configuración del Servidor MCP de Angular CLI -Angular CLI incluye un [servidor de Model Context Protocol (MCP)](https://modelcontextprotocol.io/) experimental que permite a los asistentes de IA en tu entorno de desarrollo interactuar con Angular CLI. Hemos incluido soporte para generación de código potenciado por CLI, agregar paquetes y más. +Angular CLI incluye un servidor de Model Context Protocol (MCP) que permite a los asistentes de IA (como Cursor, Antigravity, JetBrains AI, etc.) interactuar directamente con Angular CLI. Proporciona herramientas para generación de código, análisis del workspace, y ejecución de builds/pruebas. -## Herramientas Disponibles - -El servidor MCP de Angular CLI proporciona varias herramientas para asistirte en tu flujo de trabajo de desarrollo. Por defecto, las siguientes herramientas están habilitadas: - -| Nombre | Descripción | `local-only` | `read-only` | -| :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------: | :---------: | -| `ai_tutor` | Inicia un tutor de Angular interactivo potenciado por IA. Se recomienda ejecutarlo desde un nuevo proyecto Angular usando v20 o posterior. [Aprende más](ai/ai-tutor). | ✅ | ✅ | -| `find_examples` | Encuentra ejemplos de código autorizados de una base de datos curada de ejemplos oficiales y de mejores prácticas, enfocándose en características de Angular **modernas, nuevas y recientemente actualizadas**. | ✅ | ✅ | -| `get_best_practices` | Recupera la Guía de Mejores Prácticas de Angular. Esta guía es esencial para asegurar que todo el código se adhiera a los estándares modernos, incluyendo componentes standalone, formularios tipados y flujo de control moderno. | ✅ | ✅ | -| `list_projects` | Lista los nombres de todas las aplicaciones y bibliotecas definidas dentro de un espacio de trabajo de Angular. Lee el archivo de configuración `angular.json` para identificar los proyectos. | ✅ | ✅ | -| `onpush_zoneless_migration` | Analiza código Angular y proporciona un plan paso a paso e iterativo para migrarlo a detección de cambios `OnPush`, un prerrequisito para una aplicación sin zona. | ✅ | ✅ | -| `search_documentation` | Busca en la documentación oficial de Angular en . Esta herramienta debe usarse para responder cualquier pregunta sobre Angular, como para APIs, tutoriales y mejores prácticas. | ❌ | ✅ | - -### Herramientas Experimentales - -Algunas herramientas se proporcionan en estado experimental / de vista previa ya que son nuevas o no están completamente probadas. Habilítalas individualmente con la opción [`--experimental-tool`](#opciones-de-comando) y úsalas con precaución. - -| Nombre | Descripción | `local-only` | `read-only` | -| :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------: | :---------: | -| `modernize`. | Realiza migraciones de código y proporciona más instrucciones sobre cómo modernizar código Angular para alinearse con las últimas mejores prácticas y sintaxis. [Aprende más](https://angular.dev/reference/migrations) | ✅ | ❌ | + + Si tu entorno host soporta Agent Skills personalizados (como Antigravity), puedes combinar el servidor MCP de Angular CLI con las [Angular AI Skills](https://angular.dev/ai/agent-skills) oficiales. Mientras que las skills proporcionan al agente guía detallada a nivel de instrucción y estándares de codificación, el servidor MCP proporciona las herramientas de acción (como compilar, ejecutar pruebas y analizar workspaces) para ejecutar esas pautas, resultando en un agente de desarrollo completo y poderoso. + ## Get Started -Para comenzar, ejecuta el siguiente comando en tu terminal: +Para usar el servidor MCP, configura tu entorno host (IDE o CLI) para ejecutar `npx @angular/cli mcp`. -```bash -ng mcp -``` + + + Crea un archivo llamado `.antigravity/mcp.json` en la raíz de tu proyecto: -Cuando se ejecuta desde una terminal interactiva, este comando muestra instrucciones sobre cómo configurar un entorno host para usar el servidor MCP. Las siguientes secciones proporcionan configuraciones de ejemplo para varios editores y herramientas populares. + ```json + { + "mcpServers": { + "angular-cli": { + "command": "npx", + "args": ["-y", "@angular/cli", "mcp"] + } + } + } + ``` -### Cursor + -Crea un archivo llamado `.cursor/mcp.json` en la raíz de tu proyecto y agrega la siguiente configuración. También puedes configurarlo globalmente en `~/.cursor/mcp.json`. + + Crea `.cursor/mcp.json` en la raíz del proyecto (o globalmente en `~/.cursor/mcp.json`): -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] + ```json + { + "mcpServers": { + "angular-cli": { + "command": "npx", + "args": ["-y", "@angular/cli", "mcp"] + } + } } - } -} -``` + ``` -### Firebase Studio + -Crea un archivo llamado `.idx/mcp.json` en la raíz de tu proyecto y agrega la siguiente configuración: + + Crea `.vscode/mcp.json`: -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] + ```json + { + "servers": { + "angular-cli": { + "command": "npx", + "args": ["-y", "@angular/cli", "mcp"] + } + } } - } -} -``` + ``` -### Gemini CLI + + -Crea un archivo llamado `.gemini/settings.json` en la raíz de tu proyecto y agrega la siguiente configuración: +## Herramientas Disponibles (Por Defecto) {#available-tools-default} -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] - } - } -} -``` +Cuando el servidor MCP está habilitado, los agentes de IA tienen acceso a las siguientes herramientas: -### IDEs de JetBrains +| Nombre | Descripción | +| :---------------------------- | :--------------------------------------------------------------------------------------------------------- | +| `ai_tutor` | Inicia un tutor de Angular interactivo potenciado por IA. | +| `devserver.start` | Inicia de forma asíncrona un servidor de desarrollo (`ng serve`). Retorna inmediatamente. | +| `devserver.stop` | Detiene el servidor de desarrollo. | +| `devserver.wait_for_build` | Retorna los logs del build más reciente en un servidor de desarrollo en ejecución. | +| `get_best_practices` | Recupera la Guía de Mejores Prácticas de Angular (crucial para componentes standalone, formularios tipados, etc.). | +| `list_projects` | Lista todas las aplicaciones y bibliotecas en el workspace leyendo `angular.json`. | +| `onpush_zoneless_migration` | Analiza código y proporciona un plan para migrarlo a detección de cambios `OnPush` (prerrequisito para zoneless). | +| `run_target` | Ejecuta un target configurado (ej. build, test, lint, e2e, deploy). | +| `search_documentation` | Busca en la documentación oficial en `https://angular.dev`. | -En los IDEs de JetBrains (como IntelliJ IDEA o WebStorm), después de instalar el plugin JetBrains AI Assistant, ve a `Settings | Tools | AI Assistant | Model Context Protocol (MCP)`. Agrega un nuevo servidor (`+`) y selecciona `As JSON`. Luego pega la siguiente configuración: +## Flujos de Trabajo Comunes {#common-workflows} -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] - } - } -} -``` +Estos flujos de trabajo demuestran cómo los asistentes de IA coordinan diferentes herramientas MCP para lograr automáticamente historias de desarrollo complejas. -Para las instrucciones más actualizadas sobre cómo configurar servidores MCP, consulta la documentación de JetBrains: [Connect to an MCP server](https://www.jetbrains.com/help/ai-assistant/mcp.html#connect-to-an-mcp-server). +### 1. Ajuste de Rendimiento: Migración a Zoneless & OnPush {#1-performance-tuning-zoneless--onpush-migration} -### VS Code +El agente de IA optimiza el rendimiento de la detección de cambios y migra componentes a un estado listo para zoneless. -En la raíz de tu proyecto, crea un archivo llamado `.vscode/mcp.json` y agrega la siguiente configuración. Nota el uso de la propiedad `servers`. +1. **Descubrir el Workspace**: El agente de IA llama a `list_projects` para localizar componentes, proyectos y configuraciones de estilo/prueba en el workspace. +2. **Modernización de Schematics (Prerrequisito)**: El agente de IA ejecuta cualquier migración de signals prerrequisito usando comandos estándar de `ng generate` (ej. Signal Inputs, Signal Queries). +3. **Planificar la Migración**: El agente de IA llama a `onpush_zoneless_migration` con la ruta absoluta del directorio o archivo de componente. +4. **Aplicar Cambios**: El agente de IA aplica automáticamente el cambio accionable único retornado por la herramienta al código base. +5. **Verificar Cambios**: El agente de IA ejecuta pruebas unitarias llamando a `run_target` con el parámetro target establecido a `"test"`. +6. **Repetir**: El agente de IA llama a `onpush_zoneless_migration` nuevamente para obtener el siguiente paso, repitiendo hasta que la herramienta indique que la migración está completa. -```json -{ - "servers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] - } - } -} -``` +### 2. Desarrollo de Características & Ciclo TDD {#2-feature-development--tdd-loop} -### Otros IDEs +El agente de IA automatiza la investigación, implementación y verificación al desarrollar nuevas características. -Para otros IDEs, consulta la documentación de tu IDE para la ubicación adecuada del archivo de configuración MCP (a menudo `mcp.json`). La configuración debe contener el siguiente fragmento. +1. **Investigación de API y Sintaxis**: El agente de IA usa `search_documentation` para buscar APIs de Angular o reglas de sintaxis (ej. opciones del bloque `@defer`). +2. **Cargar Estándares de Codificación**: El agente de IA llama a `get_best_practices` con la ruta del workspace para cargar reglas de codificación alineadas con la versión de Angular. +3. **Iniciar Servidor de Desarrollo Local**: El agente de IA inicia un servidor en segundo plano llamando a `devserver.start`. +4. **Monitorear el Build**: El agente de IA usa `devserver.wait_for_build` para observar los logs del build y asegurar que la compilación tenga éxito mientras edita el código. +5. **Escribir y Ejecutar Pruebas**: El agente de IA identifica el framework de pruebas del proyecto (ej. Jasmine, Jest, Vitest) vía `list_projects`, escribe el archivo de prueba correspondiente, y ejecuta las pruebas usando `run_target` con `"test"`. +6. **Detener el Servidor de Desarrollo**: Al terminar, el agente de IA detiene el servidor de desarrollo activo llamando a `devserver.stop`. -```json -{ - "mcpServers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp"] - } - } -} -``` +### 3. Onboarding y Aprendizaje para Desarrolladores {#3-developer-onboarding-and-learning} -## Opciones de Comando +El agente de IA guía al desarrollador a través de conceptos de Angular en un sandbox interactivo. -El comando `mcp` puede configurarse con las siguientes opciones pasadas como argumentos en la configuración MCP de tu IDE: +1. **Descubrir Proyectos**: El agente de IA llama a `list_projects` para escanear el workspace e identificar la estructura del código base. +2. **Lanzar el Tutor**: El agente de IA ejecuta `ai_tutor` para cargar las instrucciones del currículo, la persona y las pautas de tutoría. +3. **Seguir el Currículo**: El agente de IA guía al usuario a través del currículo, explicando conceptos e instruyéndolo sobre qué componentes construir o modificar. +4. **Implementar y Verificar**: El agente de IA ayuda a implementar el código del sandbox y verifica los cambios usando `run_target` con `"test"` o `"build"`. -| Opción | Tipo | Descripción | Default | -| :---------------------------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------- | :------ | -| `--read-only` | `boolean` | Solo registra herramientas que no realizan cambios en el proyecto. Tu editor o agente de codificación aún puede realizar ediciones. | `false` | -| `--local-only` | `boolean` | Solo registra herramientas que no requieren una conexión a internet. Tu editor o agente de codificación aún puede enviar datos a través de la red. | `false` | -| `--experimental-tool`
    `-E` | `string` | Habilita una [herramienta experimental](#herramientas-experimentales). Separa múltiples opciones con espacios, ej. `-E tool_a tool_b`. | | +## Opciones de Comando {#command-options} -Por ejemplo, para ejecutar el servidor en modo de solo lectura en VS Code, actualizarías tu `mcp.json` así: +Puedes pasar argumentos al servidor MCP en el array `args` de tu configuración: -```json -{ - "servers": { - "angular-cli": { - "command": "npx", - "args": ["-y", "@angular/cli", "mcp", "--read-only"] - } - } -} -``` +- `--read-only`: Solo registra herramientas que no modifican el proyecto. +- `--local-only`: Solo registra herramientas que no requieren una conexión a internet. -## Comentarios y Nuevas Ideas +Ejemplo para modo de solo lectura: -El equipo de Angular agradece tus comentarios sobre las capacidades MCP existentes y cualquier idea que tengas para nuevas herramientas o características. Por favor comparte tus pensamientos abriendo un issue en el [repositorio de GitHub angular/angular](https://github.com/angular/angular/issues). +```json +"args": ["-y", "@angular/cli", "mcp", "--read-only"] +``` diff --git a/adev-es/src/content/ai/overview.en.md b/adev-es/src/content/ai/overview.en.md index e2da24e3..8fae0a4e 100644 --- a/adev-es/src/content/ai/overview.en.md +++ b/adev-es/src/content/ai/overview.en.md @@ -38,7 +38,7 @@ Here are examples of how to build with Genkit and Angular: - [Agentic Apps with Genkit and Angular starter-kit](https://github.com/angular/examples/tree/main/genkit-angular-starter-kit) — New to building with AI? Start here with a basic app that features an agentic workflow. Perfect place to start for your first AI building experience. -- [Use Genkit in an Angular app](https://genkit.dev/docs/angular/) — Build a basic application that uses Genkit Flows, Angular and Gemini 2.5 Flash. This step-by-step walkthrough guides you through creating a full-stack Angular application with AI features. +- [Use Genkit in an Angular app](https://genkit.dev/docs/frameworks/angular/) — Build a basic application that uses Genkit Flows, Angular and Gemini 2.5 Flash. This step-by-step walkthrough guides you through creating a full-stack Angular application with AI features. - [Dynamic Story Generator app](https://github.com/angular/examples/tree/main/genkit-angular-story-generator) — Learn to build an agentic Angular app powered by Genkit, Gemini and Imagen 3 to dynamically generate a story based on user interaction featuring beautiful image panels to accompany the events that take place. Start here if you'd like to experiment with a more advanced use-case. @@ -59,7 +59,7 @@ Here are examples of how to build with Genkit and Angular: Here is an example of how to build with Firebase AI Logic and Angular: -- [Firebase AI Logic x Angular Starter Kit](https://github.com/angular/examples/tree/main/vertex-ai-firebase-angular-example) - Use this starter-kit to build an e-commerce application with a chat agent that can perform tasks. Start here if you do not have experience building with Firebase AI Logic and Angular. +- [Firebase AI Logic x Angular Starter Kit](https://github.com/angular/examples/tree/main/firebase-ai-logic-angular-example) - Use this starter-kit to build an e-commerce application with a chat agent that can perform tasks. Start here if you do not have experience building with Firebase AI Logic and Angular. This example includes an [in-depth video walkthrough explaining the functionality and demonstrates how to add new features](https://youtube.com/live/4vfDz2al_BI). @@ -79,7 +79,7 @@ When connecting to model providers, it is important to keep your API secrets saf Your application's architecture determines which AI APIs and tools to choose. Specifically, choose based on whether or not your application is client-side or server-side. Tools such as Firebase AI Logic provide a secure connection to the model APIs for client-side code. If you want to use a different API than Firebase AI Logic or prefer to use a different model provider, consider creating a proxy-server or even [Cloud Functions for Firebase](https://firebase.google.com/docs/functions) to serve as a proxy and not expose your API keys. -For an example of connecting using a client-side app, see the code: [Firebase AI Logic Angular example repository](https://github.com/angular/examples/tree/main/vertex-ai-firebase-angular-example). +For an example of connecting using a client-side app, see the code: [Firebase AI Logic Angular example repository](https://github.com/angular/examples/tree/main/firebase-ai-logic-angular-example). For server-side connections to model APIs that require API keys, prefer using a secrets manager or environment variable, not `environments.ts`. You should follow standard best practices for securing API keys and credentials. Firebase now provides a new secrets manager with the latest updates from Firebase App Hosting. To learn more, [check out the official documentation](https://firebase.google.com/docs/app-hosting/configure). @@ -91,7 +91,7 @@ If you want to build agentic workflows, where agents are able to act and use too Tool calling further enhances your web apps by expanding your AI integration further than a question and answer style chat bot. In fact, you can empower your model to request function calls using the function calling API of your model provider. The available tools can be used to perform more complex actions within the context of your application. -In the [e-commerce example](https://github.com/angular/examples/blob/main/vertex-ai-firebase-angular-example/src/app/ai.service.ts#L88) of the [Angular examples repository](https://github.com/angular/examples), the LLM requests to make calls to functions for inventory in order to gain the necessary context to perform more complex tasks such as calculating how much a group of items in the store will cost. The scope of the available API is up to you as a developer just as is whether or not to call a function requested by the LLM. You remain in control of the flow of execution. You can expose specific functions of a service for example but not all functions of that service. +In the [e-commerce example](https://github.com/angular/examples/blob/main/firebase-ai-logic-angular-example/src/app/ai.service.ts#L88) of the [Angular examples repository](https://github.com/angular/examples), the LLM requests to make calls to functions for inventory in order to gain the necessary context to perform more complex tasks such as calculating how much a group of items in the store will cost. The scope of the available API is up to you as a developer just as is whether or not to call a function requested by the LLM. You remain in control of the flow of execution. You can expose specific functions of a service for example but not all functions of that service. ### Handling non-deterministic responses @@ -115,4 +115,5 @@ To learn about LLM prompts and AI IDE setup, see the following guides: + diff --git a/adev-es/src/content/ai/overview.md b/adev-es/src/content/ai/overview.md index 7b34780c..5674f114 100644 --- a/adev-es/src/content/ai/overview.md +++ b/adev-es/src/content/ai/overview.md @@ -14,13 +14,13 @@ Desarrollar características como estas habría requerido previamente experienci - Arquitectura sólida basada en signals diseñada para gestionar dinámicamente datos y estado - Angular se integra perfectamente con SDKs y APIs de IA -Esta guía demuestra cómo puedes usar [Genkit](/ai#construye-aplicaciones-potenciadas-por-ia-con-genkit-y-angular), [Firebase AI Logic](/ai#construye-aplicaciones-potenciadas-por-ia-con-firebase-ai-logic-y-angular) y la [Gemini API](/ai#construye-aplicaciones-potenciadas-por-ia-con-gemini-api-y-angular) para infundir IA en tus aplicaciones Angular hoy. Esta guía impulsará tu viaje de desarrollo de aplicaciones web potenciadas por IA explicando cómo comenzar a integrar IA en aplicaciones Angular. Esta guía también comparte recursos, como kits de inicio, código de ejemplo y recetas para flujos de trabajo comunes, que puedes usar para ponerte al día rápidamente. +Esta guía demuestra cómo puedes usar [Genkit](/ai#build-ai-powered-applications-with-genkit-and-angular), [Firebase AI Logic](/ai#build-ai-powered-applications-with-firebase-ai-logic-and-angular) y la [Gemini API](/ai#build-ai-powered-applications-with-gemini-api-and-angular) para infundir IA en tus aplicaciones Angular hoy. Esta guía impulsará tu viaje de desarrollo de aplicaciones web potenciadas por IA explicando cómo comenzar a integrar IA en aplicaciones Angular. Esta guía también comparte recursos, como kits de inicio, código de ejemplo y recetas para flujos de trabajo comunes, que puedes usar para ponerte al día rápidamente. Para comenzar, deberías tener un entendimiento básico de Angular. ¿Nuevo en Angular? Prueba nuestra [guía de elementos esenciales](/essentials) o nuestros [tutoriales de primeros pasos](/tutorials). NOTA: Aunque esta página presenta integraciones y ejemplos con productos de IA de Google, herramientas como Genkit son agnósticas de modelo y te permiten elegir tu propio modelo. En muchos casos, los ejemplos y muestras de código son aplicables a otras soluciones de terceros. -## Primeros Pasos +## Primeros Pasos {#getting-started} Construir aplicaciones potenciadas por IA es un campo nuevo y en rápido desarrollo. Puede ser desafiante decidir dónde empezar y qué tecnologías elegir. La siguiente sección proporciona tres opciones para elegir: @@ -30,7 +30,7 @@ Construir aplicaciones potenciadas por IA es un campo nuevo y en rápido desarro 1. _Gemini API_ te permite construir una aplicación que use los métodos y funcionalidades expuestos a través de la superficie de la API directamente, mejor para aplicaciones full-stack. Adecuado para aplicaciones que necesitan control directo sobre modelos de IA, como generación de imágenes personalizadas o procesamiento profundo de datos. -### Construye aplicaciones potenciadas por IA con Genkit y Angular +### Construye aplicaciones potenciadas por IA con Genkit y Angular {#build-ai-powered-applications-with-genkit-and-angular} [Genkit](https://genkit.dev) es un toolkit de código abierto diseñado para ayudarte a construir características potenciadas por IA en aplicaciones web y móviles. Ofrece una interfaz unificada para integrar modelos de IA de Google, OpenAI, Anthropic, Ollama y más, para que puedas explorar y elegir los mejores modelos para tus necesidades. Como solución del lado del servidor, tus aplicaciones web necesitan un entorno de servidor soportado, como un servidor basado en node para integrarse con Genkit. Construir una aplicación full-stack usando Angular SSR te da el código inicial del lado del servidor, por ejemplo. @@ -53,7 +53,7 @@ Aquí hay ejemplos de cómo construir con Genkit y Angular: Este ejemplo también tiene un video tutorial en profundidad de la funcionalidad: - [Ver "Exploring the future of web apps"](https://www.youtube.com/live/4qargCqOu70?feature=share) -### Construye aplicaciones potenciadas por IA con Firebase AI Logic y Angular +### Construye aplicaciones potenciadas por IA con Firebase AI Logic y Angular {#build-ai-powered-applications-with-firebase-ai-logic-and-angular} [Firebase AI Logic](https://firebase.google.com/products/vertex-ai-in-firebase) proporciona una forma segura de interactuar con la API de Vertex AI Gemini o la API de Imagen directamente desde tus aplicaciones web y móviles. Esto es atractivo para los desarrolladores de Angular ya que las aplicaciones pueden ser full-stack o de solo lado del cliente. Si estás desarrollando una aplicación de solo lado del cliente, Firebase AI Logic es una buena opción para incorporar IA en tus aplicaciones web. @@ -63,7 +63,7 @@ Aquí hay un ejemplo de cómo construir con Firebase AI Logic y Angular: Este ejemplo incluye un [video tutorial en profundidad explicando la funcionalidad y demuestra cómo agregar nuevas características](https://youtube.com/live/4vfDz2al_BI). -### Construye aplicaciones potenciadas por IA con Gemini API y Angular +### Construye aplicaciones potenciadas por IA con Gemini API y Angular {#build-ai-powered-applications-with-gemini-api-and-angular} La [Gemini API](https://ai.google.dev/gemini-api/docs) proporciona acceso a modelos de última generación de Google que soportan entrada de audio, imágenes, video y texto. Estos modelos están optimizados para casos de uso específicos, [aprende más en el sitio de documentación de Gemini API](https://ai.google.dev/gemini-api/docs/models). @@ -71,9 +71,9 @@ La [Gemini API](https://ai.google.dev/gemini-api/docs) proporciona acceso a mode - [Plantilla de aplicación de Chatbot con IA](https://github.com/FirebaseExtended/firebase-framework-tools/tree/main/starters/angular/ai-chatbot) - Esta plantilla comienza con una interfaz de usuario de chatbot que se comunica con la Gemini API vía HTTP. -## Mejores Prácticas +## Mejores Prácticas {#best-practices} -### Conectarse a proveedores de modelos y mantener tus Credenciales de API Seguras +### Conectarse a proveedores de modelos y mantener tus Credenciales de API Seguras {#connecting-to-model-providers-and-keeping-your-api-credentials-secure} Cuando te conectas a proveedores de modelos, es importante mantener tus secretos de API seguros. *Nunca pongas tu clave de API en un archivo que se envía al cliente, como `environments.ts`*. La arquitectura de tu aplicación determina qué APIs y herramientas de IA elegir. Específicamente, elige basándote en si tu aplicación es del lado del cliente o del lado del servidor. Herramientas como Firebase AI Logic proporcionan una conexión segura a las APIs de modelo para código del lado del cliente. Si quieres usar una API diferente a Firebase AI Logic o prefieres usar un proveedor de modelo diferente, considera crear un servidor proxy o incluso [Cloud Functions for Firebase](https://firebase.google.com/docs/functions) para servir como proxy y no exponer tus claves de API. @@ -84,14 +84,14 @@ Para conexiones del lado del servidor a APIs de modelo que requieren claves de A Para un ejemplo de conexión del lado del servidor en una aplicación full-stack, consulta el código: [repositorio de Angular AI Example (Genkit y Angular Story Generator)](https://github.com/angular/examples/tree/main/genkit-angular-story-generator). -### Usa Tool Calling para mejorar aplicaciones +### Usa Tool Calling para mejorar aplicaciones {#use-tool-calling-to-enhance-apps} Si quieres construir flujos de trabajo agénticos, donde los agentes pueden actuar y usar herramientas para resolver problemas basados en prompts, usa "tool calling". Tool calling, también conocido como function calling, es una forma de proporcionar a los LLMs la capacidad de hacer solicitudes de vuelta a la aplicación que lo llamó. Como desarrollador, defines qué herramientas están disponibles y tú tienes el control de cómo o cuándo se llaman las herramientas. Tool calling mejora aún más tus aplicaciones web expandiendo tu integración de IA más allá de un chatbot de estilo pregunta y respuesta. De hecho, puedes empoderar tu modelo para solicitar llamadas a funciones usando la API de function calling de tu proveedor de modelo. Las herramientas disponibles pueden usarse para realizar acciones más complejas dentro del contexto de tu aplicación. En el [ejemplo de e-commerce](https://github.com/angular/examples/blob/main/vertex-ai-firebase-angular-example/src/app/ai.service.ts#L88) del [repositorio de ejemplos de Angular](https://github.com/angular/examples), el LLM solicita hacer llamadas a funciones para inventario con el fin de obtener el contexto necesario para realizar tareas más complejas como calcular cuánto costará un grupo de artículos en la tienda. El alcance de la API disponible depende de ti como desarrollador, al igual que si llamar o no a una función solicitada por el LLM. Permaneces en control del flujo de ejecución. Puedes exponer funciones específicas de un servicio, por ejemplo, pero no todas las funciones de ese servicio. -### Manejo de respuestas no determinísticas +### Manejo de respuestas no determinísticas {#handling-non-deterministic-responses} Debido a que los modelos pueden devolver resultados no determinísticos, tus aplicaciones deben diseñarse con eso en mente. Aquí hay algunas estrategias que puedes usar en la implementación de tu aplicación: @@ -107,7 +107,7 @@ Considera este ejemplo: El proveedor de LLM no está respondiendo. Una estrategi - Alertar al usuario sobre la interrupción con un mensaje apropiado que no revele información sensible - Reanudar la conversación en un momento posterior una vez que los servicios estén disponibles nuevamente. -## Siguientes Pasos +## Siguientes Pasos {#next-steps} Para aprender sobre prompts de LLM y configuración de IDE con IA, consulta las siguientes guías: diff --git a/adev-es/src/content/ai/webmcp.md b/adev-es/src/content/ai/webmcp.md new file mode 100644 index 00000000..b175a4dc --- /dev/null +++ b/adev-es/src/content/ai/webmcp.md @@ -0,0 +1,259 @@ +# WebMCP + +Web Model Context Protocol (WebMCP) is an [emerging web standard](https://github.com/webmachinelearning/webmcp/) that allows web applications to expose structured tools directly to AI agents running natively in the browser. Tools defined by an application allow AI assistants to interact with it directly, providing additional capabilities to the agent and reducing the need for DOM interactions. + +For example, an application to register a new user might provide a WebMCP tool for a browser's AI agent to create the user directly rather than requiring the agent to go through a complex wizard UI via DOM interactions. + +Angular provides experimental support for WebMCP, allowing you to easily register tools tied to your application's dependency injection lifecycle and automatically turn your Signal Forms into AI-ready tools. + +IMPORTANT: The WebMCP spec is very early in its lifecycle and is undergoing frequent changes. As such, WebMCP support in Angular is currently [**experimental**](reference/releases#experimental). APIs are subject to change even outside of major versions. + +## Provide tools for the application + +Use [`provideExperimentalWebMcpTools`](api/core/provideExperimentalWebMcpTools) in your application config to register tools for the entire lifecycle of the application. Tools provided this way are automatically registered when the application initializes and unregistered when the application is destroyed. + +The `execute` callback is invoked in the injection context of the associated `Injector`, meaning you can [`inject`](api/core/inject) services directly. + +```ts {header:"main.ts"} +import {Service, inject, provideExperimentalWebMcpTools} from '@angular/core'; +import {bootstrapApplication} from '@angular/platform-browser'; +import {AppRoot} from './app-root'; + +@Service() +class Greeter { + sayHello(): string { + return 'Hello agent!'; + } +} + +bootstrapApplication(AppRoot, { + providers: [ + provideExperimentalWebMcpTools([ + { + name: 'greet', + description: 'Greets the agent.', + inputSchema: {type: 'object', properties: {}}, + execute: () => { + const greeter = inject(Greeter); + + return {content: [{type: 'text', text: greeter.sayHello()}]}; + }, + }, + ]), + ], +}); +``` + +### Define tool parameters + +When a tool requires input from the AI assistant, define the expected arguments inside `inputSchema` using [JSON Schema](https://json-schema.org/) syntax. Angular automatically infers the parameter types passed into your `execute` callback based on the schema definition. + +```ts {header:"main.ts"} +import {provideExperimentalWebMcpTools} from '@angular/core'; +import {bootstrapApplication} from '@angular/platform-browser'; +import {AppRoot} from './app-root'; + +bootstrapApplication(AppRoot, { + providers: [ + provideExperimentalWebMcpTools([ + { + name: 'searchCatalog', + description: 'Searches the store catalog for products matching a query.', + inputSchema: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'The search keywords.', + }, + maxResults: { + type: 'number', + description: 'Maximum number of results to return.', + }, + }, + required: ['query'], + additionalProperties: false, + }, + execute: ({query, maxResults}) => { + // Type of `query` is inferred as `string`. + // Type of `maxResults` is inferred as `number | undefined`. + + // Consider validating this at runtime, since inputs may not be validated to match the schema. + if (typeof query !== 'string') throw new Error(`Bad query: ${query}`); + if (typeof maxResults !== 'number' && maxResults !== undefined) + throw new Error(`Bad maxResults: ${maxResults}`); + + const limit = maxResults ?? 5; + return { + content: [{type: 'text', text: `Returning up to ${limit} results for "${query}".`}], + }; + }, + }, + ]), + ], +}); +``` + +TIP: Use `required: ['param1', 'param2', ...]` to remove `undefined` from the types of those parameters and use `additionalProperties: false` to restrict the argument object's type to only these parameters. + +## Provide tools for a route + +When building complex applications, you may only want certain tools available when the user is viewing specific routes. You can achieve this by providing tools directly in route definitions. + +```ts {header:"routes.ts"} +import {provideExperimentalWebMcpTools} from '@angular/core'; +import {Routes} from '@angular/router'; + +export const routes: Routes = [ + { + path: 'dashboard', + loadComponent: () => import('./dashboard').then((m) => m.Dashboard), + providers: [ + provideExperimentalWebMcpTools([ + { + name: 'exportDashboardReports', + description: 'Exports the current dashboard analytics.', + inputSchema: {type: 'object', properties: {}}, + execute: () => ({ + content: [{type: 'text', text: 'Dashboard export successfully triggered.'}], + }), + }, + ]), + ], + }, +]; +``` + +NOTE: When registering tools to a particular route, consider configuring the router to use [`withExperimentalAutoCleanupInjectors`](api/router/withExperimentalAutoCleanupInjectors) to ensure tools are automatically _unregistered_ when the user navigates away from the route. Without this option, WebMCP tools declared on routes will remain accessible to AI agents even after the user has navigated to a different route. + +```ts {header:"app.config.ts"} +import {ApplicationConfig} from '@angular/core'; +import {provideRouter, withExperimentalAutoCleanupInjectors} from '@angular/router'; +import {routes} from './routes'; + +export const appConfig: ApplicationConfig = { + providers: [provideRouter(routes, withExperimentalAutoCleanupInjectors())], +}; +``` + +## Provide tools within services + +For dynamic use cases, the [`declareExperimentalWebMcpTool`](api/core/declareExperimentalWebMcpTool) function registers a tool directly within an injection context and automatically unregisters it when that context is destroyed. + +```ts {header:"counter.ts"} +import {Service, declareExperimentalWebMcpTool, signal, inject} from '@angular/core'; + +@Service() +export class Counter { + readonly count = signal(0); + + constructor() { + declareExperimentalWebMcpTool({ + name: 'getCounter', + description: 'Reads the global counter.', + inputSchema: {type: 'object', properties: {}}, + execute: () => ({ + content: [{type: 'text', text: `The count is: ${this.count()}.`}], + }), + }); + } +} +``` + +While `declareExperimentalWebMcpTool` works in any injection context, watch out for [name collisions](#name-collisions) and prefer using it in root services. + +## Implicit tools in Signal Forms + +You can create a WebMCP tool implicitly from an existing Angular [Signal Form](essentials/signal-forms) with minimal configuration. Angular converts your form models into rich WebMCP tools, effectively supporting highly dynamic forms without requiring you to manually write JSON schemas or event handlers. + +### Enable the WebMCP forms feature + +First, add [`provideExperimentalWebMcpForms`](api/forms/signals/provideExperimentalWebMcpForms) to your root application providers: + +```ts {header:"main.ts"} +import {bootstrapApplication} from '@angular/platform-browser'; +import {provideExperimentalWebMcpForms} from '@angular/forms/signals'; +import {AppRoot} from './app-root'; + +bootstrapApplication(AppRoot, { + providers: [provideExperimentalWebMcpForms()], +}); +``` + +### Opt in a Signal Form + +Second, when defining a Signal Form using [`form`](api/forms/signals/form), pass the `experimentalWebMcpTool` configuration option to opt-in to an implicit WebMCP tool. Angular will inspect your form's data model and automatically generate a JSON schema for connected AI agents. + +```ts {header:"user-registration.ts"} +import {Component, signal} from '@angular/core'; +import {form, required, minLength} from '@angular/forms/signals'; + +@Component({ + selector: 'app-user-registration', + templateUrl: './user-registration.html', +}) +export class UserRegistration { + private readonly model = signal({ + firstName: '', + lastName: '', + age: 0, + hobbies: ['Web Development'], + }); + + readonly userForm = form( + this.model, + (f) => { + required(f.firstName, {message: 'First name is mandatory.'}); + required(f.lastName, {message: 'Last name is mandatory.'}); + }, + { + // Implicitly registers a WebMCP tool named `registerUser` with parameters derived from `model`. + experimentalWebMcpTool: { + name: 'registerUser', + description: 'Registers a new user.', + }, + submission: { + action: async (formValue) => { + console.log('Submitting user:', formValue); + // ... + }, + }, + }, + ); +} +``` + +In this example, Angular generates a WebMCP tool with a JSON schema which: + +1. includes `firstName`, `lastName`, `age`, and `hobbies` as parameters inferred from the initial value of the `model` signal. +2. defines `firstName` and `lastName` as _required_ fields as inferred from the [`required`](api/forms/signals/required) validator. +3. defines `hobbies` as an array of strings, allowing the agent to provide an arbitrary amount of hobbies. + +Beyond inferring the input schema, Angular also connects the WebMCP tool to the form's validation logic and submission handler. This means the agent will observe any validation errors triggered by its inputs or any failures which happen during submission, allowing it to self-correct and potentially retry. + +NOTE: Async validators are _not_ triggered and should be handled by the submission action. + +#### Constraints + +Angular infers the WebMCP schema from the initial value of your form model. This requires: + +- Concrete initial values (`''`, `0`, `false`): Angular cannot infer data types from `null` or `undefined`. +- Non-empty arrays (`['Hello!']`): Angular cannot infer data types from an empty array and requires at least one initial value. + +## Best practices + +Keep the following best practices in mind: + +### Name collisions + +WebMCP requires each tool to have a unique name and will throw an error if the same tool name is registered multiple times. This means calling `declareExperimentalWebMcpTool` or `provideExperimentalWebMcpTools` in a context where they might be registered multiple times (such as a component constructor) may lead to errors at runtime. + +Prefer placing tools on application providers, route providers, or root services where possible. When putting tools on a component, including [implicit tools in Signal Forms](#implicit-tools-in-signal-forms), ensure that component is only ever rendered on the page at most _once_ at any given time. + +### Validate tool inputs + +Angular does not provide any implicit validation that the inputs provided by an agent actually match the defined JSON schema. Consider explicitly validating arguments to the `execute` function before using them to ensure reliability. + +### Testing + +Consider using a mock WebMCP implementation like [`@mcp-b/webmcp-polyfill`](https://www.npmjs.com/package/@mcp-b/webmcp-polyfill) to effectively unit test your tools. diff --git a/adev-es/src/content/best-practices/a11y.en.md b/adev-es/src/content/best-practices/a11y.en.md index 9fb674e4..2531454d 100644 --- a/adev-es/src/content/best-practices/a11y.en.md +++ b/adev-es/src/content/best-practices/a11y.en.md @@ -10,10 +10,8 @@ This page discusses best practices for designing Angular applications that work ## Accessibility attributes - - Building accessible web experiences often involves setting [Accessible Rich Internet Applications \(ARIA\) attributes](https://web.dev/learn/accessibility/aria-html/) to provide semantic meaning where it might otherwise be missing. -Use attribute binding template syntax to control the values of accessibility-related attributes. +Use [attribute binding](guide/templates/binding#binding-dynamic-properties-and-attributes) template syntax to control the values of accessibility-related attributes. ### ARIA attributes and properties @@ -39,13 +37,10 @@ Some ARIA patterns expose DOM APIs or directive inputs that accept structured va

    Attention

    Please review your answers before continuing.

    -
    +
    - -`, + `, }) export class ReviewDialog {} ``` @@ -68,6 +63,9 @@ For example: For full details of these and other tools, see the [Angular CDK accessibility overview](https://material.angular.dev/cdk/a11y/overview). +For custom-styled components that need reusable WAI-ARIA interaction patterns, [Angular Aria](guide/aria/overview) provides headless directives for patterns such as accordion, combobox, listbox, menu, tabs, and toolbar. +These directives handle keyboard interaction, ARIA attributes, focus management, and screen reader support while letting you provide the HTML structure and styling for your application. + ### Augmenting native elements Native HTML elements capture several standard interaction patterns that are important to accessibility. @@ -101,7 +99,7 @@ The following example shows how to make a progress bar accessible by using host path="adev/src/content/examples/accessibility/src/app/progress-bar.component.ts" language="ts" linenums - highlight="[12, 20]"/> + highlight="[12, 19]"/> - - Home - - - About - - - Shop - + Home + About + Shop ``` diff --git a/adev-es/src/content/best-practices/a11y.md b/adev-es/src/content/best-practices/a11y.md index 94e38e73..429c987a 100644 --- a/adev-es/src/content/best-practices/a11y.md +++ b/adev-es/src/content/best-practices/a11y.md @@ -8,14 +8,12 @@ Para una introducción en profundidad sobre los problemas y técnicas para dise Esta página aborda las mejores prácticas para diseñar aplicaciones Angular que funcionen bien para todos los usuarios, incluidos aquellos que dependen de tecnologías de asistencia. -## Atributos de accesibilidad - - +## Atributos de accesibilidad {#accessibility-attributes} Construir experiencias web accesibles a menudo implica establecer [atributos de Aplicaciones de Internet Enriquecidas Accesibles (ARIA)](https://web.dev/learn/accessibility/aria-html/) para proporcionar significado semántico donde de otro modo podría faltar. -Usa la sintaxis de plantilla de enlace de atributo para controlar los valores de los atributos relacionados con la accesibilidad. +Usa la sintaxis de plantilla de [enlace de atributo](guide/templates/binding#binding-dynamic-properties-and-attributes) para controlar los valores de los atributos relacionados con la accesibilidad. -### Atributos y propiedades ARIA +### Atributos y propiedades ARIA {#aria-attributes-and-properties} Al enlazar a atributos ARIA en Angular, puedes usarlos directamente como cualquier otro atributo HTML. @@ -54,7 +52,7 @@ Aquí `[ariaLabelledByElements]` acepta un array de elementos, por lo que el enl Consulta la [guía de enlaces](guide/templates/binding#aria-attributes) para un resumen de la sintaxis de atributos ARIA. -## Componentes UI de Angular +## Componentes UI de Angular {#angular-ui-components} La biblioteca [Angular Material](https://material.angular.dev), que es mantenida por el equipo de Angular, es un conjunto de componentes UI reutilizables que busca ser completamente accesible. El [Component Development Kit (CDK)](https://material.angular.dev/cdk/categories) incluye el paquete `a11y` que proporciona herramientas para soportar diversas áreas de accesibilidad. @@ -68,7 +66,7 @@ Por ejemplo: Para detalles completos de estas y otras herramientas, consulta la [visión general de accesibilidad de Angular CDK](https://material.angular.dev/cdk/a11y/overview). -### Aumentando elementos nativos +### Aumentando elementos nativos {#augmenting-native-elements} Los elementos HTML nativos capturan varios patrones de interacción estándar que son importantes para la accesibilidad. Al crear componentes de Angular, debes reutilizar estos elementos nativos directamente cuando sea posible, en lugar de reimplementar comportamientos bien soportados. @@ -79,7 +77,7 @@ Esto se aplica más comúnmente a ` +``` - +```html {avoid} ``` @@ -268,8 +271,7 @@ single well-named handler. In these cases, it's fine to fall back to a name like then delegate to more specific behaviors based on the event details: ```ts - -@Component({/* ... */}) +@Component(/* ... */) class RichText { handleKeydown(event: KeyboardEvent) { if (event.ctrlKey) { @@ -278,7 +280,7 @@ class RichText { } else if (event.key === 'I') { this.activateItalic(); } -// ... + // ... } } } @@ -291,14 +293,14 @@ well-named methods to contain that logic and then _call those methods_ in your l Lifecycle hook names describe _when_ they run, meaning that the code inside doesn't have a meaningful name that describes what the code inside is doing. -```typescript -// PREFER +```ts {prefer} ngOnInit() { this.startLogging(); this.runBackgroundTask(); } +``` -// AVOID +```ts {avoid} ngOnInit() { this.logger.setMode('info'); this.logger.monitorErrors(); @@ -314,10 +316,11 @@ your class, import and `implement` these interfaces to ensure that the methods a ```ts import {Component, OnInit} from '@angular/core'; -@Component({/* ... */}) +@Component(/* ... */) export class UserProfile implements OnInit { - // The `OnInit` interface ensures this method is named correctly. - ngOnInit() { /* ... */ } + ngOnInit() { + /* ... */ + } } ``` diff --git a/adev-es/src/content/best-practices/style-guide.md b/adev-es/src/content/best-practices/style-guide.md index 80ef7466..16bf7ee7 100644 --- a/adev-es/src/content/best-practices/style-guide.md +++ b/adev-es/src/content/best-practices/style-guide.md @@ -1,6 +1,6 @@ # Guía de estilo de código Angular -## Introducción +## Introducción {#introduction} Esta guía cubre un rango de convenciones de estilo para código de aplicaciones Angular. Estas recomendaciones no son requeridas para que Angular funcione, sino que establecen un conjunto de prácticas de codificación que promueven la consistencia en el ecosistema @@ -11,25 +11,25 @@ Esta guía _no_ cubre TypeScript o prácticas generales de codificación no relacionadas con Angular. Para TypeScript, consulta la [guía de estilo TypeScript de Google](https://google.github.io/styleguide/tsguide.html). -### En caso de duda, prefiere la consistencia +### En caso de duda, prefiere la consistencia {#when-in-doubt-prefer-consistency} Siempre que te encuentres con una situación en la que estas reglas contradigan el estilo de un archivo particular, prioriza mantener la consistencia dentro de un archivo. Mezclar diferentes convenciones de estilo en un solo archivo crea más confusión que divergir de las recomendaciones en esta guía. -## Nomenclatura +## Nomenclatura {#naming} -### Separa palabras en nombres de archivo con guiones +### Separa palabras en nombres de archivo con guiones {#separate-words-in-file-names-with-hyphens} Separa palabras dentro de un nombre de archivo con guiones (`-`). Por ejemplo, un componente llamado `UserProfile` tiene un nombre de archivo `user-profile.ts`. -### Usa el mismo nombre para las pruebas de un archivo con `.spec` al final +### Usa el mismo nombre para las pruebas de un archivo con `.spec` al final {#use-the-same-name-for-a-files-tests-with-spec-at-the-end} Para pruebas unitarias, termina los nombres de archivo con `.spec.ts`. Por ejemplo, el archivo de prueba unitaria para el componente `UserProfile` tiene el nombre de archivo `user-profile.spec.ts`. -### Haz coincidir nombres de archivo con el identificador TypeScript dentro +### Haz coincidir nombres de archivo con el identificador TypeScript dentro {#match-file-names-to-the-typescript-identifier-within} Los nombres de archivo generalmente deben describir el contenido del código en el archivo. Cuando el archivo contiene una clase TypeScript, el nombre del archivo debe reflejar ese nombre de @@ -40,7 +40,7 @@ dentro. Si el código en un archivo no encaja dentro de un tema común o área de funcionalidad, considera dividir el código en diferentes archivos. Evita nombres de archivo demasiado genéricos como `helpers.ts`, `utils.ts`, o `common.ts`. -### Usa el mismo nombre de archivo para el TypeScript, plantilla y estilos de un componente +### Usa el mismo nombre de archivo para el TypeScript, plantilla y estilos de un componente {#use-the-same-file-name-for-a-components-typescript-template-and-styles} Los componentes típicamente consisten en un archivo TypeScript, un archivo de plantilla y un archivo de estilo. Estos archivos deben compartir el mismo nombre con diferentes extensiones de archivo. Por ejemplo, un componente `UserProfile` puede tener los archivos @@ -50,9 +50,9 @@ Si un componente tiene más de un archivo de estilo, agrega al nombre palabras a de ese archivo. Por ejemplo, `UserProfile` podría tener archivos de estilo `user-profile-settings.css` y `user-profile-subscription.css`. -## Estructura del proyecto +## Estructura del proyecto {#project-structure} -### Todo el código de la aplicación va en un directorio llamado `src` +### Todo el código de la aplicación va en un directorio llamado `src` {#all-the-applications-code-goes-in-a-directory-named-src} Todo tu código UI de Angular (TypeScript, HTML, y estilos) debe vivir dentro de un directorio llamado `src`. El código que no está relacionado con la UI, como archivos de configuración o scripts, @@ -61,12 +61,12 @@ debe vivir fuera del directorio `src`. Esto mantiene el directorio raíz de la aplicación consistente entre diferentes proyectos Angular y crea una separación clara entre el código UI y otro código en tu proyecto. -### Inicia tu aplicación en un archivo llamado `main.ts` directamente dentro de `src` +### Inicia tu aplicación en un archivo llamado `main.ts` directamente dentro de `src` {#bootstrap-your-application-in-a-file-named-maints-directly-inside-src} El código para iniciar, o hacer **bootstrap**, de una aplicación Angular debe siempre vivir en un archivo llamado `main.ts`. Esto representa el punto de entrada principal a la aplicación. -### Agrupa archivos estrechamente relacionados juntos en el mismo directorio +### Agrupa archivos estrechamente relacionados juntos en el mismo directorio {#group-closely-related-files-together-in-the-same-directory} Los componentes de Angular consisten en un archivo TypeScript y, opcionalmente, una plantilla y uno o más archivos de estilo. Debes agrupar estos juntos en el mismo directorio. @@ -74,7 +74,7 @@ agrupar estos juntos en el mismo directorio. Las pruebas unitarias deben vivir en el mismo directorio que el código bajo prueba. Evita recolectar pruebas no relacionadas en un solo directorio `tests`. -### Organiza tu proyecto por áreas de funcionalidad +### Organiza tu proyecto por áreas de funcionalidad {#organize-your-project-by-feature-areas} Organiza tu proyecto en subdirectorios basados en las características de tu aplicación o temas comunes al código en esos directorios. Por ejemplo, la estructura del proyecto para un sitio @@ -97,7 +97,7 @@ Evita crear subdirectorios basados en el tipo de código que vive en esos direct Evita poner tantos archivos en un directorio que se vuelva difícil de leer o navegar. A medida que el número de archivos en un directorio crece, considera dividir aún más en subdirectorios adicionales. -### Un concepto por archivo +### Un concepto por archivo {#one-concept-per-file} Prefiere enfocar archivos fuente en un solo _concepto_. Para clases Angular específicamente, esto usualmente significa un componente, directiva, o servicio por archivo. Sin embargo, está bien si un archivo contiene más de un componente o directiva si @@ -106,9 +106,9 @@ parte de un solo concepto. En caso de duda, ve con el enfoque que lleve a archivos más pequeños. -## Inyección de dependencias +## Inyección de dependencias {#dependency-injection} -### Prefiere la función `inject` sobre inyección de parámetros de constructor +### Prefiere la función `inject` sobre inyección de parámetros de constructor {#prefer-the-inject-function-over-constructor-parameter-injection} Prefiere usar la función `inject` sobre inyectar parámetros de constructor. La función `inject` funciona de la misma manera que la inyección de parámetros de constructor, pero ofrece varias ventajas de estilo: @@ -119,30 +119,30 @@ Prefiere usar la función `inject` sobre inyectar parámetros de constructor. La [Puedes refactorizar código existente a `inject` con una herramienta automática](reference/migrations/inject-function). -## Componentes y directivas +## Componentes y directivas {#components-and-directives} -### Eligiendo selectores de componentes +### Eligiendo selectores de componentes {#choosing-component-selectors} Consulta la -[guía de Componentes para detalles sobre cómo elegir selectores de componentes](guide/components/selectors#eligiendo-un-selector). +[guía de Componentes para detalles sobre cómo elegir selectores de componentes](guide/components/selectors#choosing-a-selector). -### Nombrando miembros de componentes y directivas +### Nombrando miembros de componentes y directivas {#naming-component-and-directive-members} Consulta la guía de Componentes para detalles sobre [nombrar propiedades de entrada](guide/components/inputs#choosing-input-names) y [nombrar propiedades de salida](guide/components/outputs#choosing-event-names). -### Eligiendo selectores de directivas +### Eligiendo selectores de directivas {#choosing-directive-selectors} Las directivas deben usar el -mismo [prefijo específico de aplicación](guide/components/selectors#prefijos-de-selectores) +mismo [prefijo específico de aplicación](guide/components/selectors#selector-prefixes) que tus componentes. Al usar un selector de atributo para una directiva, usa un nombre de atributo camelCase. Por ejemplo, si tu aplicación se llama "MovieReel" y construyes una directiva que agrega un tooltip a un elemento, podrías usar el selector `[mrTooltip]`. -### Agrupa propiedades específicas de Angular antes de los métodos +### Agrupa propiedades específicas de Angular antes de los métodos {#group-angular-specific-properties-before-methods} Los componentes y directivas deben agrupar las propiedades específicas de Angular juntas, típicamente cerca de la parte superior de la declaración de clase. Esto incluye dependencias inyectadas, entradas, salidas y consultas. Define estas y otras @@ -150,26 +150,26 @@ propiedades antes de los métodos de la clase. Esta práctica hace más fácil encontrar las APIs de plantilla y dependencias de la clase. -### Mantén componentes y directivas enfocados en la presentación +### Mantén componentes y directivas enfocados en la presentación {#keep-components-and-directives-focused-on-presentation} El código dentro de tus componentes y directivas generalmente debe relacionarse con la UI mostrada en la página. Para código que tiene sentido por sí solo, desacoplado de la UI, prefiere refactorizar a otros archivos. Por ejemplo, puedes factorizar reglas de validación de formularios o transformaciones de datos en funciones o clases separadas. -### Evita lógica demasiado compleja en plantillas +### Evita lógica demasiado compleja en plantillas {#avoid-overly-complex-logic-in-templates} Las plantillas de Angular están diseñadas para acomodar [expresiones similares a JavaScript](guide/templates/expression-syntax). Debes aprovechar estas expresiones para capturar lógica relativamente directa directamente en expresiones de plantilla. -Sin embargo, cuando el código en una plantilla se vuelve demasiado complejo, refactoriza la lógica al código TypeScript (típicamente con un [computed](guide/signals/overview#signals-computadas)). +Sin embargo, cuando el código en una plantilla se vuelve demasiado complejo, refactoriza la lógica al código TypeScript (típicamente con un [computed](guide/signals#computed-signals)). No hay una regla única y rápida que determine qué constituye "complejo". Usa tu mejor juicio. -### Usa `protected` en miembros de clase que solo son usados por la plantilla de un componente +### Usa `protected` en miembros de clase que solo son usados por la plantilla de un componente {#use-protected-on-class-members-that-are-only-used-by-a-components-template} Los miembros públicos de la clase de un componente intrínsecamente definen una API pública que es accesible vía inyección de dependencias y [consultas](guide/components/queries). Prefiere @@ -189,7 +189,7 @@ export class UserProfile { } ``` -### Usa `readonly` para propiedades que no deben cambiar +### Usa `readonly` para propiedades que no deben cambiar {#use-readonly-for-properties-that-shouldnt-change} Marca propiedades de componentes y directivas inicializadas por Angular como `readonly`. Esto incluye propiedades inicializadas por `input`, `model`, `output`, y consultas. El modificador de @@ -215,7 +215,7 @@ export class UserProfile { } ``` -### Prefiere `class` y `style` sobre `ngClass` y `ngStyle` +### Prefiere `class` y `style` sobre `ngClass` y `ngStyle` {#prefer-class-and-style-over-ngclass-and-ngstyle} Prefiere enlaces `class` y `style` sobre usar las directivas [`NgClass`](/api/common/NgClass) y [`NgStyle`](/api/common/NgStyle). @@ -242,7 +242,7 @@ integrada de enlace `class` y `style`. Para más detalles, consulta la [guía de enlaces](/guide/templates/binding#css-class-and-style-property-bindings) -### Nombra manejadores de eventos por lo que _hacen_, no por el evento disparador +### Nombra manejadores de eventos por lo que _hacen_, no por el evento disparador {#name-event-handlers-for-what-they-_do_-not-for-the-triggering-event} Prefiere nombrar manejadores de eventos por la acción que realizan en lugar de por el evento disparador: @@ -284,7 +284,7 @@ class RichText { } ``` -### Mantén métodos de ciclo de vida simples +### Mantén métodos de ciclo de vida simples {#keep-lifecycle-methods-simple} Evita poner lógica larga o compleja dentro de hooks de ciclo de vida como `ngOnInit`. En su lugar, prefiere crear métodos bien nombrados para contener esa lógica y luego _llamar esos métodos_ en tus hooks de ciclo de vida. Los nombres de hooks de ciclo de @@ -306,7 +306,7 @@ ngOnInit() { } ``` -### Usa interfaces de hooks de ciclo de vida +### Usa interfaces de hooks de ciclo de vida {#use-lifecycle-hook-interfaces} Angular proporciona una interfaz TypeScript para cada método de ciclo de vida. Al agregar un hook de ciclo de vida a tu clase, importa e `implementa` estas interfaces para asegurar que los métodos estén nombrados correctamente. diff --git a/adev-es/src/content/best-practices/update.en.md b/adev-es/src/content/best-practices/update.en.md index 57297149..1a7cd0d0 100644 --- a/adev-es/src/content/best-practices/update.en.md +++ b/adev-es/src/content/best-practices/update.en.md @@ -6,9 +6,9 @@ Keeping your Angular application up-to-date enables you to take advantage of lea This document contains information and resources to help you keep your Angular applications and libraries up-to-date. -For information about our versioning policy and practices —including support and deprecation practices, as well as the release schedule— see [Angular versioning and releases](reference/releases 'Angular versioning and releases'). +For information about our versioning policy and practices — including support and deprecation practices, as well as the release schedule — see [Angular versioning and releases](reference/releases 'Angular versioning and releases'). -HELPFUL: If you are currently using AngularJS, see [Upgrading from AngularJS](https://angular.io/guide/upgrade 'Upgrading from Angular JS'). +HELPFUL: If you are currently using AngularJS, see [Upgrading from AngularJS](https://angular.io/guide/upgrade 'Upgrading from AngularJS'). _AngularJS_ is the name for all v1.x versions of Angular. ## Getting notified of new releases @@ -30,7 +30,7 @@ To check your application's version of Angular use the `ng version` command from The most recent stable released version of Angular appears [on npm](https://www.npmjs.com/package/@angular/core 'Angular on npm') under "Version." For example, `16.2.4`. You can also find the most current version of Angular by using the CLI command [`ng update`](cli/update). -By default, [`ng update`](cli/update)(without additional arguments) lists the updates that are available to you. +By default, [`ng update`](cli/update) (without additional arguments) lists the updates that are available to you. ## Updating your environment and apps @@ -43,7 +43,7 @@ It also includes troubleshooting information and any recommended manual changes For simple updates, the CLI command [`ng update`](cli/update) is all you need. Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version. -[Angular Versioning and Releases](reference/releases#versioning 'Angular Release Practices, Versioning') describes the level of change that you can expect based on a release's version number. +[Angular Versioning and Releases](reference/releases#angular-versioning 'Angular Release Practices, Versioning') describes the level of change that you can expect based on a release's version number. It also describes supported update paths. ## Resource summary diff --git a/adev-es/src/content/best-practices/update.md b/adev-es/src/content/best-practices/update.md index 31a94391..16d06de0 100644 --- a/adev-es/src/content/best-practices/update.md +++ b/adev-es/src/content/best-practices/update.md @@ -11,24 +11,28 @@ Para información sobre nuestra política y prácticas de versionado —incluyen ÚTIL: Si actualmente estás usando AngularJS, consulta [Actualizando desde AngularJS](https://angular.io/guide/upgrade 'Actualizando desde Angular JS'). _AngularJS_ es el nombre para todas las versiones v1.x de Angular. -## Aprendiendo sobre nuevas funcionalidades +## Recibiendo notificaciones de nuevos lanzamientos {#getting-notified-of-new-releases} + +Para recibir notificaciones cuando nuevos lanzamientos estén disponibles, sigue a [@angular](https://x.com/angular '@angular en X') en X (anteriormente Twitter) o suscríbete al [blog de Angular](https://blog.angular.dev 'Blog de Angular'). + +## Aprendiendo sobre nuevas funcionalidades {#learning-about-new-features} ¿Qué hay de nuevo? ¿Qué ha cambiado? Compartimos las cosas más importantes que necesitas saber en el blog de Angular en [anuncios de lanzamiento](https://blog.angular.dev/ 'Blog de Angular - anuncios de lanzamiento'). Para revisar una lista completa de cambios, organizados por versión, consulta el [registro de cambios de Angular](https://github.com/angular/angular/blob/main/CHANGELOG.md 'Registro de cambios de Angular'). -## Verificando tu versión de Angular +## Verificando tu versión de Angular {#checking-your-version-of-angular} Para verificar la versión de Angular de tu aplicación usa el comando `ng version` desde dentro del directorio de tu proyecto. -## Encontrando la versión actual de Angular +## Encontrando la versión actual de Angular {#finding-the-current-version-of-angular} La versión estable más reciente lanzada de Angular aparece [en npm](https://www.npmjs.com/package/@angular/core 'Angular en npm') bajo "Version." Por ejemplo, `16.2.4`. También puedes encontrar la versión más actual de Angular usando el comando CLI [`ng update`](cli/update). Por defecto, [`ng update`](cli/update)(sin argumentos adicionales) lista las actualizaciones que están disponibles para ti. -## Actualizando tu entorno y aplicaciones +## Actualizando tu entorno y aplicaciones {#updating-your-environment-and-apps} Para hacer la actualización sencilla, proporcionamos instrucciones completas en la [Guía de Actualización de Angular](update-guide) interactiva. @@ -39,10 +43,10 @@ También incluye información de solución de problemas y cualquier cambio manua Para actualizaciones simples, el comando CLI [`ng update`](cli/update) es todo lo que necesitas. Sin argumentos adicionales, [`ng update`](cli/update) lista las actualizaciones que están disponibles para ti y proporciona pasos recomendados para actualizar tu aplicación a la versión más actual. -[Versionado y Lanzamientos de Angular](reference/releases#versioning 'Prácticas de Lanzamiento de Angular, Versionado') describe el nivel de cambio que puedes esperar basado en el número de versión de un lanzamiento. +[Versionado y Lanzamientos de Angular](reference/releases 'Prácticas de Lanzamiento de Angular, Versionado') describe el nivel de cambio que puedes esperar basado en el número de versión de un lanzamiento. También describe las rutas de actualización soportadas. -## Resumen de recursos +## Resumen de recursos {#resource-summary} - Anuncios de lanzamiento: [Blog de Angular - anuncios de lanzamiento](https://blog.angular.dev/ 'Anuncios del blog de Angular sobre lanzamientos recientes') diff --git a/adev-es/src/content/cli/index.md b/adev-es/src/content/cli/index.md deleted file mode 100644 index d6c5d061..00000000 --- a/adev-es/src/content/cli/index.md +++ /dev/null @@ -1,136 +0,0 @@ -# CLI Overview and Command Reference - -The Angular CLI is a command-line interface tool that you use to initialize, develop, scaffold, and maintain Angular applications directly from a command shell. - -## Installing Angular CLI - -Major versions of Angular CLI follow the supported major version of Angular, but minor versions can be released separately. - -Install the CLI using the `npm` package manager: - - - -npm install -g @angular/cli - - - -For details about changes between versions, and information about updating from previous releases, see the Releases tab on GitHub: https://github.com/angular/angular-cli/releases - -## Basic workflow - -Invoke the tool on the command line through the `ng` executable. -Online help is available on the command line. -Enter the following to list commands or options for a given command \(such as [new](cli/new)\) with a short description. - - - -ng --help -ng new --help - - - -To create, build, and serve a new, basic Angular project on a development server, go to the parent directory of your new workspace use the following commands: - - - -ng new my-first-project -cd my-first-project -ng serve - - - -In your browser, open http://localhost:4200/ to see the new application run. -When you use the [ng serve](cli/serve) command to build an application and serve it locally, the server automatically rebuilds the application and reloads the page when you change any of the source files. - -
    - -When you run `ng new my-first-project` a new folder, named `my-first-project`, will be created in the current working directory. -Since you want to be able to create files inside that folder, make sure you have sufficient rights in the current working directory before running the command. - -If the current working directory is not the right place for your project, you can change to a more appropriate directory by running `cd `. - -
    - -## Workspaces and project files - -The [ng new](cli/new) command creates an _Angular workspace_ folder and generates a new application skeleton. -A workspace can contain multiple applications and libraries. -The initial application created by the [ng new](cli/new) command is at the top level of the workspace. -When you generate an additional application or library in a workspace, it goes into a `projects/` subfolder. - -A newly generated application contains the source files for a root module, with a root component and template. -Each application has a `src` folder that contains the logic, data, and assets. - -You can edit the generated files directly, or add to and modify them using CLI commands. -Use the [ng generate](cli/generate) command to add new files for additional components and services, and code for new pipes, directives, and so on. -Commands such as [add](cli/add) and [generate](cli/generate), which create or operate on applications and libraries, must be executed from within a workspace or project folder. - -- See more about the [Workspace file structure](guide/file-structure). - -### Workspace and project configuration - -A single workspace configuration file, `angular.json`, is created at the top level of the workspace. -This is where you can set per-project defaults for CLI command options, and specify configurations to use when the CLI builds a project for different targets. - -The [ng config](cli/config) command lets you set and retrieve configuration values from the command line, or you can edit the `angular.json` file directly. - -
    - -**NOTE**:
    -Option names in the configuration file must use [camelCase](guide/glossary#case-types), while option names supplied to commands must be dash-case. - -
    - -- See more about [Workspace Configuration](guide/workspace-config). - -## CLI command-language syntax - -Command syntax is shown as follows: - -`ng` __ __ [*optional-arg*] `[options]` - -- Most commands, and some options, have aliases. - Aliases are shown in the syntax statement for each command. - -- Option names are prefixed with a double dash \(`--`\) characters. - Option aliases are prefixed with a single dash \(`-`\) character. - Arguments are not prefixed. - For example: - - - - ng build my-app -c production - - - -- Typically, the name of a generated artifact can be given as an argument to the command or specified with the `--name` option. - -- Arguments and option names must be given in [dash-case](guide/glossary#case-types). - For example: `--my-option-name` - -### Boolean options - -Boolean options have two forms: `--this-option` sets the flag to `true`, `--no-this-option` sets it to `false`. -If neither option is supplied, the flag remains in its default state, as listed in the reference documentation. - -### Array options - -Array options can be provided in two forms: `--option value1 value2` or `--option value1 --option value2`. - -### Relative paths - -Options that specify files can be given as absolute paths, or as paths relative to the current working directory, which is generally either the workspace or project root. - -### Schematics - -The [ng generate](cli/generate) and [ng add](cli/add) commands take, as an argument, the artifact or library to be generated or added to the current project. -In addition to any general options, each artifact or library defines its own options in a _schematic_. -Schematic options are supplied to the command in the same format as immediate command options. - - - - - - - -@reviewed 2022-02-28 diff --git a/adev-es/src/content/ecosystem/custom-build-pipeline.en.md b/adev-es/src/content/ecosystem/custom-build-pipeline.en.md index 6670076a..d2bab6eb 100644 --- a/adev-es/src/content/ecosystem/custom-build-pipeline.en.md +++ b/adev-es/src/content/ecosystem/custom-build-pipeline.en.md @@ -10,7 +10,7 @@ There are some niche use cases when you may want to maintain a custom build pipe - You have an existing app using a different toolchain and you’d like to add Angular to it - You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation) -- You’d like to create an short-lived experiment using your favorite build tool +- You’d like to create a short-lived experiment using your favorite build tool ## What are the options? diff --git a/adev-es/src/content/ecosystem/custom-build-pipeline.md b/adev-es/src/content/ecosystem/custom-build-pipeline.md index d545aba2..321042bc 100644 --- a/adev-es/src/content/ecosystem/custom-build-pipeline.md +++ b/adev-es/src/content/ecosystem/custom-build-pipeline.md @@ -4,7 +4,7 @@ Al compilar una aplicación Angular, recomendamos enfáticamente usar Angular CL Esta página explora los **casos de uso poco comunes** en los que necesitas un pipeline de compilación personalizado que no utiliza Angular CLI. Todas las herramientas listadas a continuación son plugins de compilación de código abierto mantenidos por miembros de la comunidad de Angular. Para conocer más sobre su modelo de soporte y estado de mantenimiento, revisa su documentación y las URL de sus repositorios en GitHub. -## ¿Cuándo deberías usar un pipeline de compilación personalizado? +## ¿Cuándo deberías usar un pipeline de compilación personalizado? {#when-should-you-use-a-custom-build-pipeline} Existen algunos casos muy específicos en los que podrías querer mantener un pipeline de compilación personalizado. Por ejemplo: @@ -12,7 +12,7 @@ Existen algunos casos muy específicos en los que podrías querer mantener un pi - Estás fuertemente acoplado a [module federation](https://module-federation.io/) y no puedes adoptar la [native federation](https://www.npmjs.com/package/@angular-architects/native-federation) independiente del empaquetador - Quieres crear un experimento de corta duración usando tu herramienta de compilación favorita -## ¿Cuáles son las opciones? +## ¿Cuáles son las opciones? {#what-are-the-options} Actualmente, existen dos herramientas comunitarias bien soportadas que te permiten crear un pipeline de compilación personalizado mediante un [plugin de Vite](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) y un [plugin de Rspack](https://www.npmjs.com/package/@nx/angular-rspack). Ambos utilizan las abstracciones subyacentes que impulsan Angular CLI. Te permiten crear un pipeline de compilación flexible, pero requieren mantenimiento manual y no proporcionan una experiencia de actualización automática. diff --git a/adev-es/src/content/ecosystem/rxjs-interop/output-interop.en.md b/adev-es/src/content/ecosystem/rxjs-interop/output-interop.en.md index bd9faff6..3a5fef2c 100644 --- a/adev-es/src/content/ecosystem/rxjs-interop/output-interop.en.md +++ b/adev-es/src/content/ecosystem/rxjs-interop/output-interop.en.md @@ -8,16 +8,16 @@ The `@angular/rxjs-interop` package offers two APIs related to component and dir The `outputFromObservable` lets you create a component or directive output that emits based on an RxJS observable: -```ts {highlight:[9]} +```ts {highlight:[11]} import {Directive} from '@angular/core'; import {outputFromObservable} from '@angular/core/rxjs-interop'; -@Directive({/*...*/}) +@Directive(/* ... */) class Draggable { - pointerMoves$: Observable = listenToPointerMoves(); + pointerMoves$: Observable = listenToPointerMoves(); - // Whenever `pointerMoves$` emits, the `pointerMove` event fires. - pointerMove = outputFromObservable(this.pointerMoves$); + // Whenever `pointerMoves$` emits, the `pointerMove` event fires. + pointerMove = outputFromObservable(this.pointerMoves$); } ``` @@ -35,7 +35,7 @@ The `outputToObservable` function lets you create an RxJS observable from a comp import {outputToObservable} from '@angular/core/rxjs-interop'; @Component(/*...*/) - class CustomSlider { +class CustomSlider { valueChange = output(); } diff --git a/adev-es/src/content/ecosystem/rxjs-interop/output-interop.md b/adev-es/src/content/ecosystem/rxjs-interop/output-interop.md index a22f3479..f1f5dfac 100644 --- a/adev-es/src/content/ecosystem/rxjs-interop/output-interop.md +++ b/adev-es/src/content/ecosystem/rxjs-interop/output-interop.md @@ -4,7 +4,7 @@ CONSEJO: Esta guía supone que ya estás familiarizado con los [outputs de compo El paquete `@angular/rxjs-interop` ofrece dos APIs relacionadas con los outputs de componentes y directivas. -## Crear un output basado en un Observable de RxJS +## Crear un output basado en un Observable de RxJS {#creating-an-output-based-on-an-rxjs-observable} `outputFromObservable` te permite crear un output de componente o directiva que emite en función de un Observable de RxJS: @@ -27,7 +27,7 @@ Cuando te suscribes (`subscribe`) al output, Angular reenvía automáticamente l ÚTIL: Considera usar `output()` directamente si puedes emitir valores de forma imperativa. -## Crear un Observable de RxJS a partir de un output de componente o directiva +## Crear un Observable de RxJS a partir de un output de componente o directiva {#creating-an-rxjs-observable-from-a-component-or-directive-output} La función `outputToObservable` te permite crear un Observable de RxJS a partir de un output de componente. diff --git a/adev-es/src/content/ecosystem/rxjs-interop/signals-interop.en.md b/adev-es/src/content/ecosystem/rxjs-interop/signals-interop.en.md index 2ab060e0..3e8fd13c 100644 --- a/adev-es/src/content/ecosystem/rxjs-interop/signals-interop.en.md +++ b/adev-es/src/content/ecosystem/rxjs-interop/signals-interop.en.md @@ -7,10 +7,10 @@ The `@angular/core/rxjs-interop` package offers APIs that help you integrate RxJ Use the `toSignal` function to create a signal which tracks the value of an Observable. It behaves similarly to the `async` pipe in templates, but is more flexible and can be used anywhere in an application. ```angular-ts -import { Component } from '@angular/core'; -import { AsyncPipe } from '@angular/common'; -import { interval } from 'rxjs'; -import { toSignal } from '@angular/core/rxjs-interop'; +import {Component} from '@angular/core'; +import {AsyncPipe} from '@angular/common'; +import {interval} from 'rxjs'; +import {toSignal} from '@angular/core/rxjs-interop'; @Component({ template: `{{ counter() }}`, @@ -47,7 +47,7 @@ If you don't provide an `initialValue`, the resulting signal will return `undefi Some Observables are guaranteed to emit synchronously, such as `BehaviorSubject`. In those cases, you can specify the `requireSync: true` option. -When `requiredSync` is `true`, `toSignal` enforces that the Observable emits synchronously on subscription. This guarantees that the signal always has a value, and no `undefined` type or initial value is required. +When `requireSync` is `true`, `toSignal` enforces that the Observable emits synchronously on subscription. This guarantees that the signal always has a value, and no `undefined` type or initial value is required. ### `manualCleanup` @@ -62,20 +62,20 @@ Some observables may emit values that are **equals** even though they differ by When two emitted values are considered equal, the resulting signal **does not update**. This prevents redundant computations, DOM updates, or effects from re-running unnecessarily. ```ts -import { Component } from '@angular/core'; -import { toSignal } from '@angular/core/rxjs-interop'; -import { interval, map } from 'rxjs'; +import {Component} from '@angular/core'; +import {toSignal} from '@angular/core/rxjs-interop'; +import {interval, map} from 'rxjs'; @Component(/* ... */) export class EqualExample { temperature$ = interval(1000).pipe( - map(() => ({ temperature: Math.floor(Math.random() * 3) + 20 }) ) // 20, 21, or 22 randomly + map(() => ({temperature: Math.floor(Math.random() * 3) + 20})), // 20, 21, or 22 randomly ); // Only update if the temperature changes temperature = toSignal(this.temperature$, { - initialValue: { temperature : 20 }, - equal: (prev, curr) => prev.temperature === curr.temperature + initialValue: {temperature: 20}, + equal: (prev, curr) => prev.temperature === curr.temperature, }); } ``` @@ -91,17 +91,15 @@ 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 { toObservable } from '@angular/core/rxjs-interop'; +import {Component, signal} from '@angular/core'; +import {toObservable} from '@angular/core/rxjs-interop'; @Component(/* ... */) export class SearchResults { query: Signal = inject(QueryService).query; query$ = toObservable(this.query); - results$ = this.query$.pipe( - switchMap(query => this.http.get('/search?q=' + query )) - ); + results$ = this.query$.pipe(switchMap((query) => this.http.get('/search?q=' + query))); } ``` @@ -119,7 +117,7 @@ Unlike Observables, signals never provide a synchronous notification of changes. ```ts const obs$ = toObservable(mySignal); -obs$.subscribe(value => console.log(value)); +obs$.subscribe((value) => console.log(value)); mySignal.set(1); mySignal.set(2); @@ -130,8 +128,6 @@ Here, only the last value (3) will be logged. ## Using `rxResource` for async data -IMPORTANT: `rxResource` is [experimental](reference/releases#experimental). It's ready for you to try, but it might change before it is stable. - 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 accepts an RxJS `Observable`. ```typescript @@ -146,7 +142,7 @@ export class UserProfile { protected userId = input(); private userResource = rxResource({ - params: () => ({ userId: this.userId() }), + params: () => ({userId: this.userId()}), // The `stream` property expects a factory function that returns // a data stream as an RxJS Observable. diff --git a/adev-es/src/content/ecosystem/rxjs-interop/signals-interop.md b/adev-es/src/content/ecosystem/rxjs-interop/signals-interop.md index ed19f4cb..275e3b68 100644 --- a/adev-es/src/content/ecosystem/rxjs-interop/signals-interop.md +++ b/adev-es/src/content/ecosystem/rxjs-interop/signals-interop.md @@ -2,7 +2,7 @@ El paquete `@angular/core/rxjs-interop` ofrece APIs que te ayudan a integrar RxJS y los signals de Angular. -## Crea un signal a partir de un Observable de RxJS con `toSignal` +## Crea un signal a partir de un Observable de RxJS con `toSignal` {#create-a-signal-from-an-rxjs-observable-with-tosignal} Usa la función `toSignal` para crear un signal que rastrea el valor de un Observable. Se comporta de forma similar al pipe `async` en las plantillas, pero es más flexible y puede utilizarse en cualquier parte de la aplicación. @@ -27,23 +27,23 @@ Al igual que el pipe `async`, `toSignal` se suscribe al Observable inmediatament IMPORTANTE: `toSignal` crea una suscripción. Debes evitar llamarlo repetidamente para el mismo Observable y, en su lugar, reutilizar el signal que devuelve. -### Contexto de inyección +### Contexto de inyección {#injection-context} De manera predeterminada, `toSignal` necesita ejecutarse en un [contexto de inyección](guide/di/dependency-injection-context), como durante la construcción de un componente o servicio. Si no hay un contexto de inyección disponible, puedes especificar manualmente el `Injector` que debe utilizar. -### Valores iniciales +### Valores iniciales {#initial-values} Es posible que los Observables no produzcan un valor de forma síncrona al suscribirse, pero los signals siempre requieren un valor actual. Hay varias formas de manejar este valor "inicial" de los signals de `toSignal`. -#### La opción `initialValue` +#### La opción `initialValue` {#the-initialvalue-option} Como en el ejemplo anterior, puedes especificar una opción `initialValue` con el valor que el signal debe devolver antes de que el Observable emita por primera vez. -#### Valores iniciales `undefined` +#### Valores iniciales `undefined` {#undefined-initial-values} Si no proporcionas un `initialValue`, el signal resultante devolverá `undefined` hasta que el Observable emita. Esto es similar al comportamiento del pipe `async`, que devuelve `null`. -#### La opción `requireSync` +#### La opción `requireSync` {#the-requiresync-option} Algunos Observables garantizan que emitirán de forma síncrona, por ejemplo, `BehaviorSubject`. En esos casos, puedes especificar la opción `requireSync: true`. @@ -55,7 +55,7 @@ De forma predeterminada, `toSignal` da de baja automáticamente del Observable c Para reemplazar este comportamiento, puedes pasar la opción `manualCleanup`. Puedes utilizar este ajuste para Observables que se completan de forma natural. -#### Comparación de igualdad personalizada +#### Comparación de igualdad personalizada {#custom-equality-comparison} Algunos Observables pueden emitir valores que son **iguales** aunque difieran por referencia o en detalles menores. La opción `equal` te permite definir una **función de igualdad personalizada** para determinar cuándo dos valores consecutivos deben considerarse iguales. @@ -80,13 +80,13 @@ export class EqualExample { } ``` -### Error y finalización +### Error y finalización {#error-and-completion} Si un Observable usado en `toSignal` produce un error, dicho error se lanza cuando se lee el signal. Si un Observable usado en `toSignal` se completa, el signal continúa devolviendo el valor emitido más recientemente antes de completarse. -## Crea un Observable de RxJS a partir de un signal con `toObservable` +## Crea un Observable de RxJS a partir de un signal con `toObservable` {#create-an-rxjs-observable-from-a-signal-with-toobservable} Usa la utilidad `toObservable` para crear un `Observable` que rastrea el valor de un signal. El valor del signal se supervisa con un `effect` que emite el valor al Observable cuando cambia. @@ -107,11 +107,11 @@ export class SearchResults { Cuando el signal `query` cambia, el Observable `query$` emite la consulta más reciente y desencadena una nueva solicitud HTTP. -### Contexto de inyección +### Contexto de inyección {#injection-context-1} De forma predeterminada, `toObservable` necesita ejecutarse en un [contexto de inyección](guide/di/dependency-injection-context), como durante la construcción de un componente o servicio. Si no hay un contexto de inyección disponible, puedes especificar manualmente el `Injector` que se debe utilizar. -### Sincronización de `toObservable` +### Sincronización de `toObservable` {#timing-of-toobservable} `toObservable` utiliza un effect para rastrear el valor del signal en un `ReplaySubject`. Al suscribirse, el primer valor (si está disponible) puede emitirse de forma síncrona y todos los valores posteriores serán asíncronos. @@ -128,7 +128,7 @@ mySignal.set(3); Aquí, solo se registrará el último valor (3). -## Usar `rxResource` para datos asíncronos +## Usar `rxResource` para datos asíncronos {#using-rxresource-for-async-data} IMPORTANTE: `rxResource` es [experimental](reference/releases#experimental). Está listo para que lo pruebes, pero podría cambiar antes de ser estable. diff --git a/adev-es/src/content/ecosystem/rxjs-interop/take-until-destroyed.en.md b/adev-es/src/content/ecosystem/rxjs-interop/take-until-destroyed.en.md index 19e8a429..fc94b13d 100644 --- a/adev-es/src/content/ecosystem/rxjs-interop/take-until-destroyed.en.md +++ b/adev-es/src/content/ecosystem/rxjs-interop/take-until-destroyed.en.md @@ -18,7 +18,7 @@ export class UserProfile { // This subscription the 'notifications' Observable is automatically // unsubscribed when the 'UserProfile' component is destroyed. const messages: Observable = this.dispatcher.notifications; - messages.pipe(takeUntilDestroyed()).subscribe(message => { + messages.pipe(takeUntilDestroyed()).subscribe((message) => { this.popup.show(message); }); } @@ -38,7 +38,7 @@ export class UserProfile { // Always pass a `DestroyRef` if you call `takeUntilDestroyed` outside // of an injection context. const messages: Observable = this.dispatcher.notifications; - messages.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(message => { + messages.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((message) => { this.popup.show(message); }); } diff --git a/adev-es/src/content/ecosystem/rxjs-interop/take-until-destroyed.md b/adev-es/src/content/ecosystem/rxjs-interop/take-until-destroyed.md index d0fea668..a29d104e 100644 --- a/adev-es/src/content/ecosystem/rxjs-interop/take-until-destroyed.md +++ b/adev-es/src/content/ecosystem/rxjs-interop/take-until-destroyed.md @@ -25,7 +25,7 @@ export class UserProfile { } ``` -El operador `takeUntilDestroyed` acepta un único argumento opcional [`DestroyRef`](/api/core/DestroyRef). El operador usa `DestroyRef` para saber cuándo el componente o la directiva se han destruido. Puedes omitir este argumento cuando llames a `takeUntilDestroyed` en un [contexto de inyección](https://angular.dev/guide/di/dependency-injection-context), normalmente el constructor de un componente o directiva. Proporciona siempre un `DestroyRef` si tu código puede llamar a `takeUntilDestroyed` fuera de un contexto de inyección. +El operador `takeUntilDestroyed` acepta un único argumento opcional [`DestroyRef`](/api/core/DestroyRef). El operador usa `DestroyRef` para saber cuándo el componente o la directiva se han destruido. Puedes omitir este argumento cuando llames a `takeUntilDestroyed` en un [contexto de inyección](/guide/di/dependency-injection-context), normalmente el constructor de un componente o directiva. Proporciona siempre un `DestroyRef` si tu código puede llamar a `takeUntilDestroyed` fuera de un contexto de inyección. ```typescript @Component(/* ... */) diff --git a/adev-es/src/content/ecosystem/service-workers/app-shell.en.md b/adev-es/src/content/ecosystem/service-workers/app-shell.en.md index 31e86a0a..7a7d9afc 100644 --- a/adev-es/src/content/ecosystem/service-workers/app-shell.en.md +++ b/adev-es/src/content/ecosystem/service-workers/app-shell.en.md @@ -26,7 +26,7 @@ For more information about this command, see [App shell command](cli/generate/ap The command updates the application code and adds extra files to the project structure. - +```text src ├── app │ ├── app.config.server.ts # server application configuration @@ -36,7 +36,7 @@ src │ ├── app-shell.component.spec.ts │ └── app-shell.component.ts └── main.server.ts # main server application bootstrapping - +``` diff --git a/adev-es/src/content/ecosystem/service-workers/communications.en.md b/adev-es/src/content/ecosystem/service-workers/communications.en.md index 71e9ffe5..22822c68 100644 --- a/adev-es/src/content/ecosystem/service-workers/communications.en.md +++ b/adev-es/src/content/ecosystem/service-workers/communications.en.md @@ -24,7 +24,7 @@ The `versionUpdates` is an `Observable` property of `SwUpdate` and emits five ev | `VersionInstallationFailedEvent` | Emitted when the installation of a new version failed. It may be used for logging/monitoring purposes. | | `VersionFailedEvent` | Emitted when a version encounters a critical failure (such as broken hash errors) that affects all clients using that version. Provides error details for debugging and transparency. | - + ### Checking for updates @@ -56,7 +56,7 @@ Alternatively, you might want to define a different [registration strategy](api/ You can update an existing tab to the latest version by reloading the page as soon as a new version is ready. To avoid disrupting the user's progress, it is generally a good idea to prompt the user and let them confirm that it is OK to reload the page and update to the latest version: - + Calling `activateUpdate()` updates a tab to the latest version without reloading the page, but this could break the application. @@ -96,7 +96,7 @@ That particular application version is broken and there is no way to fix the sta In such cases, the service worker notifies the client by sending an `UnrecoverableStateEvent` event. Subscribe to `SwUpdate#unrecoverable` to be notified and handle these errors. - + ## More on Angular service workers diff --git a/adev-es/src/content/ecosystem/service-workers/communications.md b/adev-es/src/content/ecosystem/service-workers/communications.md index 602f63fb..ea2a56cd 100644 --- a/adev-es/src/content/ecosystem/service-workers/communications.md +++ b/adev-es/src/content/ecosystem/service-workers/communications.md @@ -2,7 +2,7 @@ Habilitar el soporte de service worker implica algo más que registrarlo; también proporciona servicios que puedes usar para interactuar con el service worker y controlar la caché de tu aplicación. -## Servicio `SwUpdate` +## Servicio `SwUpdate` {#swupdate-service} El servicio `SwUpdate` te da acceso a eventos que indican cuándo el service worker descubre e instala una actualización disponible para tu aplicación. @@ -12,7 +12,7 @@ El servicio `SwUpdate` admite tres operaciones diferentes: - Pedirle al service worker que verifique en el servidor si hay nuevas actualizaciones. - Pedirle al service worker que active la versión más reciente de la aplicación para la pestaña actual. -### Actualizaciones de versión +### Actualizaciones de versión {#version-updates} `versionUpdates` es una propiedad `Observable` de `SwUpdate` y emite cinco tipos de eventos: @@ -26,7 +26,7 @@ El servicio `SwUpdate` admite tres operaciones diferentes: -### Verificando actualizaciones +### Verificando actualizaciones {#checking-for-updates} Es posible pedirle al service worker que revise si se desplegó alguna actualización en el servidor. El service worker busca actualizaciones durante la inicialización y en cada solicitud de navegación—es decir, cuando la persona usuaria navega desde una dirección diferente hacia tu aplicación. @@ -51,7 +51,7 @@ Evita ese retraso esperando primero a que la aplicación se estabilice antes de Como alternativa, puedes definir una [estrategia de registro](api/service-worker/SwRegistrationOptions#registrationStrategy) diferente para el ServiceWorker. -### Actualizando a la versión más reciente +### Actualizando a la versión más reciente {#updating-to-the-latest-version} Puedes actualizar una pestaña existente a la versión más reciente recargando la página tan pronto como una nueva versión esté lista. Para evitar interrumpir el progreso de la persona usuaria, en general es recomendable mostrar un aviso y pedir que confirme si desea recargar la página y actualizar a la versión más reciente: @@ -66,7 +66,7 @@ Actualizar sin recargar puede crear una discrepancia de versiones entre el shell Debes usar `activateUpdate()` solo si estás seguro de que es seguro para tu caso específico. -### Manejar un estado irrecuperable +### Manejar un estado irrecuperable {#handling-an-unrecoverable-state} En algunos casos, la versión de la aplicación que el service worker usa para atender a una persona usuaria puede quedar en un estado roto que no se puede recuperar sin recargar completamente la página. @@ -98,7 +98,7 @@ Suscríbete a `SwUpdate#unrecoverable` para recibir la notificación y manejar e -## Más sobre los service workers de Angular +## Más sobre los service workers de Angular {#more-on-angular-service-workers} También podría interesarte lo siguiente: diff --git a/adev-es/src/content/ecosystem/service-workers/config.en.md b/adev-es/src/content/ecosystem/service-workers/config.en.md index 8f133394..d0ea43ba 100644 --- a/adev-es/src/content/ecosystem/service-workers/config.en.md +++ b/adev-es/src/content/ecosystem/service-workers/config.en.md @@ -85,7 +85,7 @@ For example, an asset group that matches `/foo.js` should appear before one that Each asset group specifies both a group of resources and a policy that governs them. This policy determines when the resources are fetched and what happens when changes are detected. -Asset groups follow the Typescript interface shown here: +Asset groups follow the TypeScript interface shown here: ```ts interface AssetGroup { @@ -179,7 +179,7 @@ The first data group that matches the requested resource handles the request. It is recommended that you put the more specific data groups higher in the list. For example, a data group that matches `/api/foo.json` should appear before one that matches `/api/*.json`. -Data groups follow this Typescript interface: +Data groups follow this TypeScript interface: ```ts export interface DataGroup { @@ -354,14 +354,12 @@ The URL query is ignored when matching. If the field is omitted, it defaults to: ```ts - [ -'/**', // Include all URLs. -'!/**/*.*', // Exclude URLs to files (containing a file extension in the last segment). -'!/**/*__*', // Exclude URLs containing `__` in the last segment. -'!/**/*__*/**', // Exclude URLs containing `__` in any other segment. -] - + '/**', // Include all URLs. + '!/**/*.*', // Exclude URLs to files (containing a file extension in the last segment). + '!/**/*__*', // Exclude URLs containing `__` in the last segment. + '!/**/*__*/**', // Exclude URLs containing `__` in any other segment. +]; ``` ### `navigationRequestStrategy` @@ -369,7 +367,6 @@ If the field is omitted, it defaults to: This optional property enables you to configure how the service worker handles navigation requests: ```json - { "navigationRequestStrategy": "freshness" } diff --git a/adev-es/src/content/ecosystem/service-workers/config.md b/adev-es/src/content/ecosystem/service-workers/config.md index e69de29b..8852f94a 100644 --- a/adev-es/src/content/ecosystem/service-workers/config.md +++ b/adev-es/src/content/ecosystem/service-workers/config.md @@ -0,0 +1,384 @@ +# Archivo de configuración del Service Worker + +Este tema describe las propiedades del archivo de configuración del service worker. + +## Modificar la configuración {#modifying-the-configuration} + +El archivo de configuración JSON `ngsw-config.json` especifica qué archivos y URLs de datos debe cachear el service worker de Angular y cómo debe actualizar los archivos y datos cacheados. +El [Angular CLI](tools/cli) procesa este archivo de configuración durante `ng build`. + +Todas las rutas de archivos deben comenzar con `/`, lo que corresponde al directorio de despliegue — generalmente `dist/` en proyectos CLI. + +A menos que se indique lo contrario, los patrones usan un formato glob **limitado\*** que internamente se convertirá en regex: + +| Formatos glob | Detalles | +| :------------ | :-------------------------------------------------------------------------------------------------------------------- | +| `**` | Coincide con 0 o más segmentos de ruta | +| `*` | Coincide con 0 o más caracteres excluyendo `/` | +| `?` | Coincide exactamente con un carácter excluyendo `/` | +| Prefijo `!` | Marca el patrón como negativo, lo que significa que solo se incluyen archivos que no coincidan con el patrón | + + +Ten en cuenta que algunos caracteres con significado especial en una expresión regular no son escapados y el patrón tampoco se envuelve en `^`/`$` en la conversión interna de glob a regex. + +`$` es un carácter especial en regex que coincide con el final de la cadena y no se escapará automáticamente al convertir el patrón glob a una expresión regular. + +Si deseas hacer coincidir literalmente el carácter `$`, debes escaparlo tú mismo (con `\\$`). Por ejemplo, el patrón glob `/foo/bar/$value` resulta en una expresión sin coincidencia posible, porque es imposible tener una cadena que tenga caracteres después de que haya terminado. + +El patrón no se envolverá automáticamente en `^` y `$` al convertirlo a una expresión regular. Por lo tanto, los patrones coincidirán parcialmente con las URLs de solicitud. + +Si deseas que tus patrones coincidan con el principio y/o el final de las URLs, puedes agregar `^`/`$` tú mismo. Por ejemplo, el patrón glob `/foo/bar/*.js` coincidirá con archivos `.js` y `.json`. Si solo deseas coincidir con archivos `.js`, usa `/foo/bar/*.js$`. + + +Patrones de ejemplo: + +| Patrones | Detalles | +| :----------- | :---------------------------------------------- | +| `/**/*.html` | Especifica todos los archivos HTML | +| `/*.html` | Especifica solo los archivos HTML en la raíz | +| `!/**/*.map` | Excluye todos los sourcemaps | + +## Propiedades de configuración del service worker {#service-worker-configuration-properties} + +Las siguientes secciones describen cada propiedad del archivo de configuración. + +### `appData` + +Esta sección te permite pasar cualquier dato que quieras para describir esta versión particular de la aplicación. +El servicio `SwUpdate` incluye esos datos en las notificaciones de actualización. +Muchas aplicaciones usan esta sección para proporcionar información adicional para mostrar popups de UI, notificando a los usuarios sobre la actualización disponible. + +### `index` + +Especifica el archivo que sirve como página de índice para satisfacer solicitudes de navegación. +Generalmente es `/index.html`. + +### `assetGroups` + +Los _assets_ son recursos que forman parte de la versión de la aplicación que se actualizan junto con la aplicación. +Pueden incluir recursos cargados desde el origen de la página, así como recursos de terceros cargados desde CDNs y otras URLs externas. +Como no todas esas URLs externas pueden conocerse en el momento de la compilación, se pueden hacer coincidir patrones de URL. + +HELPFUL: Para que el service worker maneje recursos cargados desde diferentes orígenes, asegúrate de que [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) esté correctamente configurado en el servidor de cada origen. + +Este campo contiene un array de grupos de assets, cada uno de los cuales define un conjunto de recursos de assets y la política por la cual se cachean. + +```ts +{ + "assetGroups": [ + { + … + }, + { + … + } + ] +} +``` + +HELPFUL: Cuando el ServiceWorker maneja una solicitud, verifica los grupos de assets en el orden en que aparecen en `ngsw-config.json`. +El primer grupo de assets que coincide con el recurso solicitado maneja la solicitud. + +Se recomienda colocar los grupos de assets más específicos al principio de la lista. +Por ejemplo, un grupo de assets que coincide con `/foo.js` debería aparecer antes que uno que coincida con `*.js`. + +Cada grupo de assets especifica tanto un grupo de recursos como una política que los rige. +Esta política determina cuándo se obtienen los recursos y qué sucede cuando se detectan cambios. + +Los grupos de assets siguen la interfaz TypeScript mostrada aquí: + +```ts +interface AssetGroup { + name: string; + installMode?: 'prefetch' | 'lazy'; + updateMode?: 'prefetch' | 'lazy'; + resources: { + files?: string[]; + urls?: string[]; + }; + cacheQueryOptions?: { + ignoreSearch?: boolean; + }; +} +``` + +Cada `AssetGroup` se define por las siguientes propiedades del grupo de assets. + +#### `name` + +Un `name` es obligatorio. +Identifica este grupo particular de assets entre versiones de la configuración. + +#### `installMode` + +El `installMode` determina cómo se cachean inicialmente estos recursos. +El `installMode` puede ser uno de dos valores: + +| Valores | Detalles | +| :--------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `prefetch` | Le indica al service worker de Angular que obtenga cada recurso listado mientras cachea la versión actual de la aplicación. Esto consume mucho ancho de banda pero garantiza que los recursos estén disponibles cuando se soliciten, incluso si el navegador está actualmente sin conexión. | +| `lazy` | No cachea ninguno de los recursos de antemano. En cambio, el service worker de Angular solo cachea recursos para los que recibe solicitudes. Este es un modo de caché bajo demanda. Los recursos que nunca se solicitan no se cachean. Esto es útil para cosas como imágenes en diferentes resoluciones, por lo que el service worker solo cachea los assets correctos para la pantalla y orientación específicas. | + +Por defecto es `prefetch`. + +#### `updateMode` + +Para recursos que ya están en la caché, el `updateMode` determina el comportamiento de caché cuando se descubre una nueva versión de la aplicación. +Cualquier recurso en el grupo que haya cambiado desde la versión anterior se actualiza de acuerdo con `updateMode`. + +| Valores | Detalles | +| :--------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `prefetch` | Le indica al service worker que descargue y cachee los recursos modificados de inmediato. | +| `lazy` | Le indica al service worker que no cachee esos recursos. En cambio, los trata como no solicitados y espera hasta que se soliciten nuevamente antes de actualizarlos. Un `updateMode` de `lazy` solo es válido si el `installMode` también es `lazy`. | + +Por defecto es el valor en que está configurado `installMode`. + +#### `resources` + +Esta sección describe los recursos a cachear, divididos en los siguientes grupos: + +| Grupos de recursos | Detalles | +| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `files` | Lista patrones que coinciden con archivos en el directorio de distribución. Pueden ser archivos individuales o patrones tipo glob que coinciden con varios archivos. | +| `urls` | Incluye tanto URLs como patrones de URL que se hacen coincidir en tiempo de ejecución. Estos recursos no se obtienen directamente y no tienen hashes de contenido, pero se cachean según sus encabezados HTTP. Esto es más útil para CDNs como el servicio Google Fonts.
    _(Los patrones glob negativos no son compatibles y `?` se hará coincidir literalmente; es decir, no coincidirá con ningún carácter que no sea `?`.)_ | + +#### `cacheQueryOptions` + +Estas opciones se usan para modificar el comportamiento de coincidencia de las solicitudes. +Se pasan a la función `Cache#match` del navegador. +Consulta [MDN](https://developer.mozilla.org/docs/Web/API/Cache/match) para más detalles. +Actualmente, solo se admiten las siguientes opciones: + +| Opciones | Detalles | +| :------------- | :---------------------------------------------------- | +| `ignoreSearch` | Ignora los parámetros de consulta. Por defecto `false`. | + +### `dataGroups` + +A diferencia de los recursos de assets, las solicitudes de datos no están versionadas junto con la aplicación. +Se cachean según políticas configuradas manualmente que son más útiles para situaciones como solicitudes de API y otras dependencias de datos. + +Este campo contiene un array de grupos de datos, cada uno de los cuales define un conjunto de recursos de datos y la política por la cual se cachean. + +```json +{ + "dataGroups": [ + { + … + }, + { + … + } + ] +} +``` + +HELPFUL: Cuando el ServiceWorker maneja una solicitud, verifica los grupos de datos en el orden en que aparecen en `ngsw-config.json`. +El primer grupo de datos que coincide con el recurso solicitado maneja la solicitud. + +Se recomienda colocar los grupos de datos más específicos al principio de la lista. +Por ejemplo, un grupo de datos que coincide con `/api/foo.json` debería aparecer antes que uno que coincida con `/api/*.json`. + +Los grupos de datos siguen esta interfaz TypeScript: + +```ts +export interface DataGroup { + name: string; + urls: string[]; + version?: number; + cacheConfig: { + maxSize: number; + maxAge: string; + timeout?: string; + refreshAhead?: string; + strategy?: 'freshness' | 'performance'; + }; + cacheQueryOptions?: { + ignoreSearch?: boolean; + }; +} +``` + +Cada `DataGroup` se define por las siguientes propiedades del grupo de datos. + +#### `name` + +Similar a `assetGroups`, cada grupo de datos tiene un `name` que lo identifica de forma única. + +#### `urls` + +Una lista de patrones de URL. +Las URLs que coincidan con estos patrones se cachean de acuerdo con la política de este grupo de datos. +Solo se cachean las solicitudes no mutantes (GET y HEAD). + +- Los patrones glob negativos no son compatibles +- `?` se hace coincidir literalmente; es decir, coincide _solo_ con el carácter `?` + +#### `version` + +Ocasionalmente las APIs cambian sus formatos de manera que no es compatible con versiones anteriores. +Una nueva versión de la aplicación podría no ser compatible con el formato de API anterior y, por lo tanto, podría no ser compatible con los recursos cacheados existentes de esa API. + +`version` proporciona un mecanismo para indicar que los recursos que se están cacheando se han actualizado de forma incompatible con versiones anteriores, y que las entradas de caché antiguas —las de versiones anteriores— deben descartarse. + +`version` es un campo entero y por defecto es `1`. + +#### `cacheConfig` + +Las siguientes propiedades definen la política por la que se cachean las solicitudes coincidentes. + +##### `maxSize` + +El número máximo de entradas, o respuestas, en la caché. + +CRITICAL: Las cachés sin límite pueden crecer de manera ilimitada y eventualmente exceder las cuotas de almacenamiento, resultando en expulsión. + +##### `maxAge` + +El parámetro `maxAge` indica cuánto tiempo se permite que las respuestas permanezcan en la caché antes de considerarse inválidas y ser expulsadas. `maxAge` es una cadena de duración, usando los siguientes sufijos de unidad: + +| Sufijos | Detalles | +| :------ | :------------ | +| `d` | Días | +| `h` | Horas | +| `m` | Minutos | +| `s` | Segundos | +| `u` | Milisegundos | + +Por ejemplo, la cadena `3d12h` cachea contenido por hasta tres días y medio. + +##### `timeout` + +Esta cadena de duración especifica el timeout de red. +El timeout de red es cuánto tiempo espera el service worker de Angular a que la red responda antes de usar una respuesta cacheada, si está configurado para hacerlo. +`timeout` es una cadena de duración, usando los siguientes sufijos de unidad: + +| Sufijos | Detalles | +| :------ | :------------ | +| `d` | Días | +| `h` | Horas | +| `m` | Minutos | +| `s` | Segundos | +| `u` | Milisegundos | + +Por ejemplo, la cadena `5s30u` se traduce en cinco segundos y 30 milisegundos de timeout de red. + +##### `refreshAhead` + +Esta cadena de duración especifica el tiempo previo a la expiración de un recurso cacheado cuando el service worker de Angular debería intentar proactivamente actualizar el recurso desde la red. +La duración `refreshAhead` es una configuración opcional que determina cuánto tiempo antes de la expiración de una respuesta cacheada debe el service worker iniciar una solicitud para actualizar el recurso desde la red. + +| Sufijos | Detalles | +| :------ | :------------ | +| `d` | Días | +| `h` | Horas | +| `m` | Minutos | +| `s` | Segundos | +| `u` | Milisegundos | + +Por ejemplo, la cadena `1h30m` se traduce en una hora y 30 minutos antes del tiempo de expiración. + +##### `strategy` + +El service worker de Angular puede usar cualquiera de dos estrategias de caché para recursos de datos. + +| Estrategias de caché | Detalles | +| :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `performance` | El valor por defecto, optimiza para respuestas lo más rápidas posible. Si un recurso existe en la caché, se usa la versión cacheada y no se realiza ninguna solicitud de red. Esto permite cierta obsolescencia, dependiendo del `maxAge`, a cambio de un mejor rendimiento. Es adecuado para recursos que no cambian con frecuencia; por ejemplo, imágenes de avatar de usuario. | +| `freshness` | Optimiza la actualidad de los datos, obteniendo preferentemente los datos solicitados de la red. Solo si la red agota el tiempo de espera, según `timeout`, la solicitud recurre a la caché. Esto es útil para recursos que cambian con frecuencia; por ejemplo, saldos de cuentas. | + +HELPFUL: También puedes emular una tercera estrategia, [staleWhileRevalidate](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate), que devuelve datos cacheados si están disponibles, pero también obtiene datos frescos de la red en segundo plano para la próxima vez. +Para usar esta estrategia, configura `strategy` como `freshness` y `timeout` como `0u` en `cacheConfig`. + +Esto esencialmente hace lo siguiente: + +1. Intentar obtener de la red primero. +2. Si la solicitud de red no se completa de inmediato, es decir, después de un timeout de 0 ms, ignorar la antigüedad de la caché y recurrir al valor cacheado. +3. Una vez que la solicitud de red se completa, actualizar la caché para solicitudes futuras. +4. Si el recurso no existe en la caché, esperar la solicitud de red de todos modos. + +##### `cacheOpaqueResponses` + +Si el service worker de Angular debe cachear respuestas opacas o no. + +Si no se especifica, el valor por defecto depende de la estrategia configurada del grupo de datos: + +| Estrategias | Detalles | +| :------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Grupos con la estrategia `freshness` | El valor por defecto es `true` y el service worker cachea respuestas opacas. Estos grupos solicitarán los datos cada vez y solo recurrirán a la respuesta cacheada cuando estén sin conexión o en una red lenta. Por lo tanto, no importa si el service worker cachea una respuesta de error. | +| Grupos con la estrategia `performance` | El valor por defecto es `false` y el service worker no cachea respuestas opacas. Estos grupos continuarían devolviendo una respuesta cacheada hasta que `maxAge` expire, incluso si el error se debió a un problema temporal de red o servidor. Por lo tanto, sería problemático que el service worker cacheara una respuesta de error. | + + + +En caso de que no estés familiarizado, una [respuesta opaca](https://fetch.spec.whatwg.org#concept-filtered-response-opaque) es un tipo especial de respuesta que se devuelve al solicitar un recurso que está en un origen diferente que no devuelve encabezados CORS. +Una de las características de una respuesta opaca es que el service worker no puede leer su estado, lo que significa que no puede verificar si la solicitud fue exitosa o no. +Consulta [Introducción a `fetch()`](https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types) para más detalles. + +Si no puedes implementar CORS — por ejemplo, si no controlas el origen — es preferible usar la estrategia `freshness` para recursos que resulten en respuestas opacas. + + + +#### `cacheQueryOptions` + +Consulta [assetGroups](#assetgroups) para más detalles. + +### `navigationUrls` + +Esta sección opcional te permite especificar una lista personalizada de URLs que serán redirigidas al archivo de índice. + +#### Manejo de solicitudes de navegación {#handling-navigation-requests} + +El ServiceWorker redirige las solicitudes de navegación que no coinciden con ningún grupo de `asset` o `data` al [archivo de índice](#index) especificado. +Una solicitud se considera una solicitud de navegación si: + +- Su [método](https://developer.mozilla.org/docs/Web/API/Request/method) es `GET` +- Su [modo](https://developer.mozilla.org/docs/Web/API/Request/mode) es `navigation` +- Acepta una respuesta `text/html` según lo determinado por el valor del encabezado `Accept` +- Su URL coincide con los siguientes criterios: + - La URL no debe contener una extensión de archivo (es decir, un `.`) en el último segmento de ruta + - La URL no debe contener `__` + +HELPFUL: Para configurar si las solicitudes de navegación se envían a través de la red o no, consulta las secciones [navigationRequestStrategy](#navigationrequeststrategy) y [applicationMaxAge](#applicationmaxage). + +#### Hacer coincidir URLs de solicitudes de navegación {#matching-navigation-request-urls} + +Aunque estos criterios predeterminados están bien en la mayoría de los casos, a veces es deseable configurar reglas diferentes. +Por ejemplo, es posible que desees ignorar rutas específicas, como las que no forman parte de la aplicación Angular, y pasarlas al servidor. + +Este campo contiene un array de URLs y patrones de URL [tipo glob](#modifying-the-configuration) que se hacen coincidir en tiempo de ejecución. +Puede contener tanto patrones negativos (es decir, patrones que comienzan con `!`) como patrones y URLs no negativos. + +Solo las solicitudes cuyas URLs coincidan con _cualquiera_ de las URLs/patrones no negativos y _ninguno_ de los negativos se consideran solicitudes de navegación. +La consulta de URL se ignora al hacer la coincidencia. + +Si el campo se omite, por defecto es: + +```ts +[ + '/**', // Incluir todas las URLs. + '!/**/*.*', // Excluir URLs de archivos (que contienen una extensión de archivo en el último segmento). + '!/**/*__*', // Excluir URLs que contienen `__` en el último segmento. + '!/**/*__*/**', // Excluir URLs que contienen `__` en cualquier otro segmento. +]; +``` + +### `navigationRequestStrategy` + +Esta propiedad opcional te permite configurar cómo el service worker maneja las solicitudes de navegación: + +```json +{ + "navigationRequestStrategy": "freshness" +} +``` + +| Valores posibles | Detalles | +| :--------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `'performance'` | La configuración por defecto. Sirve el [archivo de índice](#index) especificado, que generalmente está cacheado. | +| `'freshness'` | Pasa las solicitudes a través de la red y recurre al comportamiento `performance` cuando está sin conexión. Este valor es útil cuando el servidor redirige las solicitudes de navegación a otro lugar usando un código de estado HTTP de redirección `3xx`. Las razones para usar este valor incluyen:
    • Redirigir a un sitio web de autenticación cuando la autenticación no es manejada por la aplicación
    • Redirigir URLs específicas para evitar romper enlaces/marcadores existentes después de un rediseño del sitio web
    • Redirigir a un sitio web diferente, como una página de estado del servidor, mientras una página está temporalmente inactiva
    | + +IMPORTANT: La estrategia `freshness` generalmente resulta en más solicitudes enviadas al servidor, lo que puede aumentar la latencia de respuesta. Se recomienda usar la estrategia de rendimiento por defecto siempre que sea posible. + +### `applicationMaxAge` + +Esta propiedad opcional te permite configurar cuánto tiempo el service worker cacheará cualquier solicitud. Dentro del `maxAge`, los archivos se servirán desde la caché. Más allá de eso, todas las solicitudes solo se servirán desde la red, incluidas las solicitudes de assets y datos. diff --git a/adev-es/src/content/ecosystem/service-workers/custom-service-worker-scripts.en.md b/adev-es/src/content/ecosystem/service-workers/custom-service-worker-scripts.en.md index d5fdcaa6..2a5dbaea 100644 --- a/adev-es/src/content/ecosystem/service-workers/custom-service-worker-scripts.en.md +++ b/adev-es/src/content/ecosystem/service-workers/custom-service-worker-scripts.en.md @@ -73,8 +73,8 @@ importScripts('./ngsw-worker.js'); 3. Configure the service worker registration to use your custom script: ```ts -import { ApplicationConfig, isDevMode } from '@angular/core'; -import { provideServiceWorker } from '@angular/service-worker'; +import {ApplicationConfig, isDevMode} from '@angular/core'; +import {provideServiceWorker} from '@angular/service-worker'; export const appConfig: ApplicationConfig = { providers: [ diff --git a/adev-es/src/content/ecosystem/service-workers/custom-service-worker-scripts.md b/adev-es/src/content/ecosystem/service-workers/custom-service-worker-scripts.md index b9672368..228b4a3b 100644 --- a/adev-es/src/content/ecosystem/service-workers/custom-service-worker-scripts.md +++ b/adev-es/src/content/ecosystem/service-workers/custom-service-worker-scripts.md @@ -2,7 +2,7 @@ Aunque el service worker de Angular ofrece excelentes capacidades, es posible que necesites agregar funcionalidad personalizada, como manejar notificaciones push, sincronización en segundo plano u otros eventos de service worker. Puedes crear un script de service worker personalizado que importe y amplíe el service worker de Angular. -## Crear un service worker personalizado +## Crear un service worker personalizado {#creating-a-custom-service-worker} Para crear un service worker personalizado que extienda la funcionalidad de Angular: @@ -86,7 +86,7 @@ export const appConfig: ApplicationConfig = { }; ``` -### Mejores prácticas para service workers personalizados +### Mejores prácticas para service workers personalizados {#best-practices-for-custom-service-workers} Al extender el service worker de Angular: @@ -96,7 +96,7 @@ Al extender el service worker de Angular: - **Prueba exhaustivamente** en entornos de desarrollo y producción. - **Maneja los errores con cuidado** para evitar que tu código personalizado rompa la funcionalidad del service worker de Angular. -### Casos de uso comunes +### Casos de uso comunes {#common-use-cases} Los service workers personalizados se utilizan con frecuencia para: diff --git a/adev-es/src/content/ecosystem/service-workers/devops.en.md b/adev-es/src/content/ecosystem/service-workers/devops.en.md index fb3b0ea9..498cceab 100644 --- a/adev-es/src/content/ecosystem/service-workers/devops.en.md +++ b/adev-es/src/content/ecosystem/service-workers/devops.en.md @@ -138,7 +138,7 @@ The Angular service worker exposes debugging information under the `ngsw/` virtu Currently, the single exposed URL is `ngsw/state`. Here is an example of this debug page's contents: - +```shell {hideCopy} NGSW Debug Info: @@ -160,17 +160,17 @@ Task queue: Debug log: - +``` #### Driver state The first line indicates the driver state: - +```shell {hideCopy} Driver state: NORMAL ((nominal)) - +``` `NORMAL` indicates that the service worker is operating normally and is not in a degraded state. @@ -190,21 +190,21 @@ The new instance starts in the `NORMAL` mode, regardless of the state of the pre #### Latest manifest hash - +```shell {hideCopy} Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c - +``` This is the SHA1 hash of the most up-to-date version of the application that the service worker knows about. #### Last update check - +```shell {hideCopy} Last update check: never - +``` This indicates the last time the service worker checked for a new version, or update, of the application. `never` indicates that the service worker has never checked for an update. @@ -213,13 +213,13 @@ In this example debug file, the update check is currently scheduled, as explaine #### Version - +```shell {hideCopy} === Version eea7f5f464f90789b621170af5a569d6be077e5c === Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65 - +``` In this example, the service worker has one version of the application cached and being used to serve two different tabs. @@ -227,7 +227,7 @@ HELPFUL: This version hash is the "latest manifest hash" listed above. Both clie #### Idle task queue - +```shell {hideCopy} === Idle Task Queue === Last update tick: 1s496u @@ -236,7 +236,7 @@ Task queue: - init post-load (update, cleanup) - +``` The Idle Task Queue is the queue of all pending tasks that happen in the background in the service worker. If there are any tasks in the queue, they are listed with a description. @@ -248,11 +248,11 @@ The "Last update run" counter shows the last time idle tasks were actually execu #### Debug log - +```shell {hideCopy} Debug log: - +``` Errors that occur within the service worker are logged here. @@ -283,7 +283,7 @@ When the service worker's request for `ngsw.json` returns a `404`, then the serv -A small script, `safety-worker.js`, is also included in the `@angular/service-worker` NPM package. +A small script, `safety-worker.js`, is also included in the `@angular/service-worker` npm package. When loaded, it un-registers itself from the browser and removes the service worker caches. This script can be used as a last resort to get rid of unwanted service workers already installed on client pages. diff --git a/adev-es/src/content/ecosystem/service-workers/devops.md b/adev-es/src/content/ecosystem/service-workers/devops.md index 53e8dd56..64256f04 100644 --- a/adev-es/src/content/ecosystem/service-workers/devops.md +++ b/adev-es/src/content/ecosystem/service-workers/devops.md @@ -3,19 +3,19 @@ Esta página es una referencia para desplegar y dar soporte a aplicaciones en producción que usan el service worker de Angular. Explica cómo encaja el service worker de Angular en el entorno de producción, cuál es su comportamiento bajo distintas condiciones y qué recursos y mecanismos de seguridad están disponibles. -## Service worker y almacenamiento en caché de recursos de la aplicación +## Service worker y almacenamiento en caché de recursos de la aplicación {#service-worker-and-caching-of-application-resources} Imagina el service worker de Angular como una caché de reenvío o como un CDN en el borde instalado en el navegador del usuario final. El service worker responde a las solicitudes que hace la aplicación Angular de recursos o datos desde una caché local, sin necesidad de esperar a la red. Como cualquier caché, tiene reglas sobre cómo caduca el contenido y cómo se actualiza. -### Versiones de la aplicación +### Versiones de la aplicación {#application-versions} En el contexto de un service worker de Angular, una "versión" es un conjunto de recursos que representan una compilación específica de la aplicación Angular. Cada vez que se despliega una nueva compilación de la aplicación, el service worker trata esa compilación como una nueva versión. Esto es cierto incluso si solo se actualizó un archivo. En un momento dado, el service worker puede tener múltiples versiones de la aplicación en la caché y podría estar sirviéndolas simultáneamente. -Para obtener más información, consulta la sección [Pestañas de la aplicación](#pestañas-de-la-aplicación). +Para obtener más información, consulta la sección [Pestañas de la aplicación](#application-tabs). Para preservar la integridad de la aplicación, el service worker de Angular agrupa todos los archivos en una versión conjunta. Los archivos agrupados en una versión suelen incluir HTML, JS y CSS. @@ -37,19 +37,19 @@ Si alguno de los archivos cacheados cambia, el hash del archivo cambia en `ngsw. Con el comportamiento de versionado del service worker de Angular, un servidor de aplicaciones puede garantizar que la aplicación Angular siempre tenga un conjunto de archivos coherente. -#### Verificaciones de actualización +#### Verificaciones de actualización {#update-checks} Cada vez que el usuario abre o actualiza la aplicación, el service worker de Angular verifica si hay actualizaciones revisando si `ngsw.json` cambió. Si encuentra una actualización, la descarga y la almacena en caché automáticamente, y la sirve la próxima vez que se cargue la aplicación. -### Integridad de los recursos +### Integridad de los recursos {#resource-integrity} Uno de los posibles efectos secundarios de un almacenamiento en caché prolongado es guardar accidentalmente un recurso que no es válido. En una caché HTTP normal, una actualización forzada o el vencimiento de la caché limitan los efectos negativos de almacenar un archivo no válido. Un service worker ignora esas restricciones y, efectivamente, almacena en caché la aplicación completa durante mucho tiempo. Es importante que el service worker obtenga el contenido correcto, por lo que conserva hashes de los recursos para mantener su integridad. -#### Contenido con hash +#### Contenido con hash {#hashed-content} Para asegurar la integridad de los recursos, el service worker de Angular valida los hashes de todos los recursos para los que dispone de un hash. En una aplicación creada con la [Angular CLI](tools/cli), esto incluye todo lo que haya en el directorio `dist` cubierto por la configuración `src/ngsw-config.json` del usuario. @@ -65,7 +65,7 @@ Los errores de hash pueden ocurrir por varios motivos: - Errores durante el proceso de compilación podrían generar recursos actualizados sin que `ngsw.json` se actualice Lo inverso también podría suceder: que `ngsw.json` se actualice sin que cambien los recursos. -#### Contenido sin hash +#### Contenido sin hash {#unhashed-content} Los únicos recursos que tienen hashes en el manifiesto `ngsw.json` son los recursos que estaban presentes en el directorio `dist` cuando se generó el manifiesto. Otros recursos, especialmente los que se cargan desde CDNs, tienen contenido que es desconocido en tiempo de compilación o se actualiza con más frecuencia de la que se despliega la aplicación. @@ -75,10 +75,10 @@ El service worker de Angular continúa sirviendo un recurso incluso después de ya no es válido. Al mismo tiempo, intenta actualizar el recurso vencido en segundo plano. De esta manera, los recursos sin hash que estén rotos no permanecen en la caché más allá de su vida útil configurada. -### Pestañas de la aplicación +### Pestañas de la aplicación {#application-tabs} Puede ser problemático para una aplicación si la versión de los recursos que recibe cambia repentinamente o sin aviso. -Consulta la sección [Versiones de la aplicación](#versiones-de-la-aplicación) para ver la descripción de este tipo de problemas. +Consulta la sección [Versiones de la aplicación](#application-versions) para ver la descripción de este tipo de problemas. El service worker de Angular ofrece una garantía: una aplicación en ejecución continúa usando la misma versión de la aplicación. Si se abre otra instancia de la aplicación en una nueva pestaña del navegador, se sirve la versión más reciente de la aplicación. @@ -98,7 +98,7 @@ Otras razones por las que el service worker puede cambiar la versión de una apl - La página se vuelve a cargar o actualizar. - La página solicita que se active una actualización de inmediato usando el servicio `SwUpdate`. -### Actualizaciones del service worker +### Actualizaciones del service worker {#service-worker-updates} El service worker de Angular es un script pequeño que se ejecuta en los navegadores web. De vez en cuando, el service worker se actualiza con correcciones de errores y mejoras. @@ -110,7 +110,7 @@ La mayoría de las actualizaciones del service worker de Angular son transparent Ocasionalmente, una corrección de errores o una mejora puede requerir la invalidación de cachés antiguos. En ese caso, el service worker actualiza la aplicación desde la red de forma transparente. -### Omitir el service worker +### Omitir el service worker {#bypassing-the-service-worker} En algunos casos, podrías querer omitir el service worker por completo y dejar que el navegador maneje la solicitud. Un ejemplo es cuando dependes de una característica que actualmente no está soportada en los service workers, como [reportar el progreso en cargas de archivos](https://github.com/w3c/ServiceWorker/issues/1141). @@ -118,21 +118,21 @@ Un ejemplo es cuando dependes de una característica que actualmente no está so Para omitir el service worker, establece `ngsw-bypass` como encabezado de solicitud o como parámetro de consulta. El valor del encabezado o del parámetro de consulta se ignora y puede estar vacío u omitirse. -### Solicitudes del service worker cuando el servidor no puede alcanzarse +### Solicitudes del service worker cuando el servidor no puede alcanzarse {#service-worker-requests-when-the-server-cant-be-reached} -El service worker procesa todas las solicitudes a menos que se [omita explícitamente](#omitir-el-service-worker). +El service worker procesa todas las solicitudes a menos que se [omita explícitamente](#bypassing-the-service-worker). Dependiendo del estado y la configuración de la caché, el service worker devuelve una respuesta cacheada o envía la solicitud al servidor. El service worker solo almacena en caché las respuestas a solicitudes no mutables, como `GET` y `HEAD`. Si el service worker recibe un error del servidor o no obtiene respuesta, retorna un código de error que indica el resultado de la llamada. Por ejemplo, si el service worker no recibe respuesta, crea un estado [504 Gateway Timeout](https://developer.mozilla.org/es/docs/Web/HTTP/Reference/Status/504) para devolverlo. El estado `504` en este ejemplo puede aparecer porque el servidor está sin conexión o el cliente se desconectó. -## Depurar el service worker de Angular +## Depurar el service worker de Angular {#debugging-the-angular-service-worker} Ocasionalmente, puede ser necesario examinar el service worker de Angular en ejecución para investigar problemas o confirmar que funciona como se espera. Los navegadores ofrecen herramientas integradas para depurar service workers y el propio service worker de Angular incluye funcionalidades de depuración útiles. -### Ubicar y analizar información de depuración +### Ubicar y analizar información de depuración {#locating-and-analyzing-debugging-information} El service worker de Angular expone información de depuración bajo el directorio virtual `ngsw/`. Actualmente, la única URL expuesta es `ngsw/state`. @@ -162,7 +162,7 @@ Debug log:
    -#### Estado del driver +#### Estado del driver {#driver-state} La primera línea indica el estado del driver: @@ -188,7 +188,7 @@ Ambos estados son temporales; solo se conservan durante la vida útil de la [ins El navegador a veces finaliza un service worker inactivo para conservar memoria y capacidad de procesamiento, y crea uno nuevo en respuesta a eventos de red. La nueva instancia comienza en modo `NORMAL`, independientemente del estado de la instancia anterior. -#### Último hash de manifiesto +#### Último hash de manifiesto {#latest-manifest-hash} @@ -198,7 +198,7 @@ Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c Este es el hash SHA1 de la versión más actual de la aplicación conocida por el service worker. -#### Última verificación de actualización +#### Última verificación de actualización {#last-update-check} @@ -211,7 +211,7 @@ Indica la última vez que el service worker verificó si había una nueva versi En este ejemplo de depuración, la verificación de actualización está programada, como se explica en la siguiente sección. -#### Versión +#### Versión {#version} @@ -225,7 +225,7 @@ En este ejemplo, el service worker tiene una versión de la aplicación cacheada ÚTIL: Este hash de versión es el "latest manifest hash" listado anteriormente. Ambos clientes están en la versión más reciente. Cada cliente se lista por su ID del API `Clients` del navegador. -#### Cola de tareas inactivas +#### Cola de tareas inactivas {#idle-task-queue} @@ -246,7 +246,7 @@ Los contadores "Last update tick/run" muestran el tiempo desde eventos específi "Last update run" muestra la última vez que se ejecutaron tareas inactivas. "Last update tick" muestra el tiempo transcurrido desde el último evento después del cual la cola podría procesarse. -#### Registro de depuración +#### Registro de depuración {#debug-log} @@ -256,7 +256,7 @@ Debug log: Los errores que ocurren dentro del service worker se registran aquí. -### Herramientas para desarrolladores +### Herramientas para desarrolladores {#developer-tools} Navegadores como Chrome proporcionan herramientas para interactuar con service workers. Estas herramientas pueden ser muy poderosas cuando se usan correctamente, pero hay algunas consideraciones a tener en cuenta. @@ -269,12 +269,12 @@ Estas herramientas pueden ser muy poderosas cuando se usan correctamente, pero h - Detener y reiniciar el service worker en el panel de Service Worker dispara una verificación de actualización. -## Seguridad del service worker +## Seguridad del service worker {#service-worker-safety} Los errores o configuraciones incorrectas podrían causar que el service worker de Angular actúe de maneras inesperadas. Si esto sucede, el service worker de Angular contiene varios mecanismos de emergencia en caso de que un administrador necesite desactivarlo rápidamente. -### Mecanismo a prueba de fallos +### Mecanismo a prueba de fallos {#fail-safe} Para desactivar el service worker, cambia el nombre del archivo `ngsw.json` o elimínalo. Cuando la solicitud del service worker a `ngsw.json` devuelve un `404`, el service worker elimina todas sus cachés y se desregistra, esencialmente autodestruyéndose. @@ -295,7 +295,7 @@ En su lugar, debes servir el contenido de `safety-worker.js` en la URL del scrip Para la mayoría de los sitios, esto significa que deberías servir el safety worker en la URL antigua del Service Worker de forma permanente. Este script puede usarse para desactivar `@angular/service-worker` y eliminar las cachés correspondientes. También elimina cualquier otro Service Worker que se haya servido en el pasado en tu sitio. -### Cambiar la ubicación de tu aplicación +### Cambiar la ubicación de tu aplicación {#changing-your-applications-location} IMPORTANTE: Los service workers no funcionan detrás de una redirección. Es posible que ya te hayas encontrado con el error `The script resource is behind a redirect, which is disallowed`. @@ -305,9 +305,9 @@ Si configuras una redirección desde la ubicación anterior, como `example.com`, Además, la redirección ni siquiera se activará para las personas usuarias que cargan el sitio completamente desde el service worker. El worker antiguo, registrado en `example.com`, intenta actualizarse y envía una solicitud a la ubicación anterior `example.com`. Esta solicitud se redirige a la nueva ubicación `www.example.com` y produce el error: `The script resource is behind a redirect, which is disallowed`. -Para solucionar esto, quizá tengas que desactivar el worker antiguo usando una de las técnicas anteriores: [Mecanismo a prueba de fallos](#mecanismo-a-prueba-de-fallos) o [Safety worker](#safety-worker). +Para solucionar esto, quizá tengas que desactivar el worker antiguo usando una de las técnicas anteriores: [Mecanismo a prueba de fallos](#fail-safe) o [Safety worker](#safety-worker). -## Más sobre los service workers de Angular +## Más sobre los service workers de Angular {#more-on-angular-service-workers} También podría interesarte lo siguiente: diff --git a/adev-es/src/content/ecosystem/service-workers/getting-started.en.md b/adev-es/src/content/ecosystem/service-workers/getting-started.en.md index 268a3a18..a99bb75c 100644 --- a/adev-es/src/content/ecosystem/service-workers/getting-started.en.md +++ b/adev-es/src/content/ecosystem/service-workers/getting-started.en.md @@ -166,9 +166,8 @@ Angular service workers support comprehensive configuration options through the The `enabled` option controls whether the service worker will be registered and related services will attempt to communicate with it. ```ts - -import { ApplicationConfig, isDevMode } from '@angular/core'; -import { provideServiceWorker } from '@angular/service-worker'; +import {ApplicationConfig, isDevMode} from '@angular/core'; +import {provideServiceWorker} from '@angular/service-worker'; export const appConfig: ApplicationConfig = { providers: [ @@ -177,7 +176,6 @@ export const appConfig: ApplicationConfig = { }), ], }; - ``` ### Cache control with updateViaCache @@ -185,7 +183,6 @@ export const appConfig: ApplicationConfig = { The `updateViaCache` option controls how the browser consults the HTTP cache during service worker updates. This provides fine-grained control over when the browser fetches updated service worker scripts and imported modules. ```ts - export const appConfig: ApplicationConfig = { providers: [ provideServiceWorker('ngsw-worker.js', { @@ -194,7 +191,6 @@ export const appConfig: ApplicationConfig = { }), ], }; - ``` The `updateViaCache` option accepts the following values: @@ -208,7 +204,6 @@ The `updateViaCache` option accepts the following values: The `type` option enables specifying the script type when registering service workers, providing support for ES module features in your service worker scripts. ```ts - export const appConfig: ApplicationConfig = { providers: [ provideServiceWorker('ngsw-worker.js', { @@ -217,7 +212,6 @@ export const appConfig: ApplicationConfig = { }), ], }; - ``` The `type` option accepts the following values: @@ -230,7 +224,6 @@ The `type` option accepts the following values: The `scope` option defines the service worker's registration scope, determining what range of URLs it can control. ```ts - export const appConfig: ApplicationConfig = { providers: [ provideServiceWorker('ngsw-worker.js', { @@ -239,7 +232,6 @@ export const appConfig: ApplicationConfig = { }), ], }; - ``` - Controls which URLs the service worker can intercept and manage @@ -251,7 +243,6 @@ export const appConfig: ApplicationConfig = { The `registrationStrategy` option defines when the service worker will be registered with the browser, providing control over the timing of registration. ```ts - export const appConfig: ApplicationConfig = { providers: [ provideServiceWorker('ngsw-worker.js', { @@ -260,7 +251,6 @@ export const appConfig: ApplicationConfig = { }), ], }; - ``` Available registration strategies: @@ -270,7 +260,6 @@ Available registration strategies: - **`'registerWithDelay:timeout'`** - Register with a delay of the specified timeout in milliseconds ```ts - // Register immediately export const immediateConfig: ApplicationConfig = { providers: [ @@ -290,13 +279,12 @@ export const delayedConfig: ApplicationConfig = { }), ], }; - ``` You can also provide an Observable factory function for custom registration timing: ```ts -import { timer } from 'rxjs'; +import {timer} from 'rxjs'; export const customConfig: ApplicationConfig = { providers: [ @@ -306,7 +294,6 @@ export const customConfig: ApplicationConfig = { }), ], }; - ``` ## More on Angular service workers diff --git a/adev-es/src/content/ecosystem/service-workers/getting-started.md b/adev-es/src/content/ecosystem/service-workers/getting-started.md index c950ca72..d7053a5a 100644 --- a/adev-es/src/content/ecosystem/service-workers/getting-started.md +++ b/adev-es/src/content/ecosystem/service-workers/getting-started.md @@ -2,7 +2,7 @@ Este documento explica cómo habilitar el soporte del service worker de Angular en proyectos que creaste con la [Angular CLI](tools/cli). Luego utiliza un ejemplo para mostrar un service worker en acción, demostrando la carga y el almacenamiento en caché básico. -## Agregar un service worker a tu proyecto +## Agregar un service worker a tu proyecto {#adding-a-service-worker-to-your-project} Para configurar el service worker de Angular en tu proyecto, ejecuta el siguiente comando de la CLI: @@ -34,7 +34,7 @@ ng build El proyecto de la CLI ya está configurado para usar el service worker de Angular. -## Service worker en acción: un recorrido +## Service worker en acción: un recorrido {#service-worker-in-action-a-tour} Esta sección demuestra un service worker en acción mediante una aplicación de ejemplo. Para habilitar el soporte de service worker durante el desarrollo local, usa la configuración de producción con el siguiente comando: @@ -56,7 +56,7 @@ npx http-server -p 8080 -c-1 dist//browser Esto servirá tu aplicación con soporte de service worker en http://localhost:8080. -### Carga inicial +### Carga inicial {#initial-load} Con el servidor ejecutándose en el puerto `8080`, apunta tu navegador a `http://localhost:8080`. Tu aplicación debería cargarse normalmente. @@ -65,7 +65,7 @@ CONSEJO: Cuando pruebes los service workers de Angular, es buena idea usar una v ÚTIL: Si no estás usando HTTPS, el service worker solo se registrará cuando accedas a la aplicación en `localhost`. -### Simular un problema de red +### Simular un problema de red {#simulating-a-network-issue} Para simular un problema de red, deshabilita la interacción de red para tu aplicación. @@ -92,7 +92,7 @@ Mira la pestaña Network para verificar que el service worker esté activo. Esto significa que los recursos no se están cargando desde la red. En cambio, se cargan desde la caché del service worker. -### ¿Qué se está almacenando en caché? +### ¿Qué se está almacenando en caché? {#whats-being-cached} Observa que todos los archivos que el navegador necesita para renderizar esta aplicación están cacheados. La configuración predeterminada `ngsw-config.json` está configurada para almacenar en caché los recursos específicos usados por la CLI: @@ -108,7 +108,7 @@ IMPORTANTE: El `ngsw-config.json` generado incluye una lista limitada de extensi IMPORTANTE: Si `resourcesOutputPath` o las rutas de `assets` se modifican después de generar el archivo de configuración, debes cambiar las rutas manualmente en `ngsw-config.json`. -### Realizar cambios en tu aplicación +### Realizar cambios en tu aplicación {#making-changes-to-your-application} Ahora que viste cómo los service workers almacenan en caché tu aplicación, el siguiente paso es comprender cómo funcionan las actualizaciones. Haz un cambio en la aplicación y observa cómo el service worker instala la actualización: @@ -129,7 +129,7 @@ Haz un cambio en la aplicación y observa cómo el service worker instala la act npx http-server -p 8080 -c-1 dist//browser ``` -### Actualizar tu aplicación en el navegador +### Actualizar tu aplicación en el navegador {#updating-your-application-in-the-browser} Ahora observa cómo el navegador y el service worker manejan la aplicación actualizada. @@ -157,11 +157,11 @@ Ahora observa cómo el navegador y el service worker manejan la aplicación actu El service worker instaló la versión actualizada de tu aplicación _en segundo plano_, y la próxima vez que se carga o recarga la página, el service worker cambia a la versión más reciente. -## Configuración del service worker +## Configuración del service worker {#service-worker-configuration} Los service workers de Angular admiten opciones de configuración exhaustivas mediante la interfaz `SwRegistrationOptions`, que proporciona control detallado sobre el registro, la caché y la ejecución de scripts. -### Habilitar y deshabilitar service workers +### Habilitar y deshabilitar service workers {#enabling-and-disabling-service-workers} La opción `enabled` controla si se registrará el service worker y si los servicios relacionados intentarán comunicarse con él. @@ -180,7 +180,7 @@ export const appConfig: ApplicationConfig = { ``` -### Control de caché con updateViaCache +### Control de caché con updateViaCache {#cache-control-with-updateviacache} La opción `updateViaCache` controla cómo el navegador consulta la caché HTTP durante las actualizaciones del service worker. Esto brinda control detallado sobre cuándo el navegador obtiene scripts actualizados del service worker y módulos importados. @@ -203,7 +203,7 @@ La opción `updateViaCache` acepta los siguientes valores: - **`'all'`**: la caché HTTP se consulta tanto para el script del service worker como para sus scripts importados - **`'none'`**: la caché HTTP no se consulta ni para el script del service worker ni para sus scripts importados -### Compatibilidad con módulos ES mediante la opción type +### Compatibilidad con módulos ES mediante la opción type {#es-module-support-with-type-option} La opción `type` permite especificar el tipo de script al registrar service workers, lo que brinda soporte a las características de módulos ES en tus scripts de service worker. @@ -225,7 +225,7 @@ La opción `type` acepta los siguientes valores: - **`'classic'`** (predeterminado): ejecución tradicional de scripts de service worker. No se permiten características de módulos ES como `import` y `export` - **`'module'`**: registra el script como un módulo ES. Permite usar la sintaxis `import`/`export` y las características de módulos -### Control del alcance de registro +### Control del alcance de registro {#registration-scope-control} La opción `scope` define el alcance de registro del service worker, determinando qué rango de URL puede controlar. @@ -246,7 +246,7 @@ export const appConfig: ApplicationConfig = { - De forma predeterminada, el scope es el directorio que contiene el script del service worker - Se usa al llamar a `ServiceWorkerContainer.register()` -### Configuración de la estrategia de registro +### Configuración de la estrategia de registro {#registration-strategy-configuration} La opción `registrationStrategy` define cuándo se registrará el service worker en el navegador, lo que proporciona control sobre el momento del registro. @@ -309,7 +309,7 @@ export const customConfig: ApplicationConfig = { ``` -## Más sobre los service workers de Angular +## Más sobre los service workers de Angular {#more-on-angular-service-workers} También podría interesarte lo siguiente: diff --git a/adev-es/src/content/ecosystem/service-workers/overview.md b/adev-es/src/content/ecosystem/service-workers/overview.md index b6bd0fff..61544686 100644 --- a/adev-es/src/content/ecosystem/service-workers/overview.md +++ b/adev-es/src/content/ecosystem/service-workers/overview.md @@ -20,7 +20,7 @@ Si el service worker está diseñado para hacerlo, puede *satisfacer por complet Incluso en redes rápidas y confiables, la latencia de ida y vuelta puede introducir demoras significativas al cargar la aplicación. Usar un service worker para reducir la dependencia de la red puede mejorar significativamente la experiencia de usuario. -## Service workers en Angular +## Service workers en Angular {#service-workers-in-angular} Las aplicaciones Angular, al ser aplicaciones de una sola página, están en una posición ideal para aprovechar las ventajas de los service workers. Angular incluye una implementación de service worker. Las personas desarrolladoras pueden aprovechar este service worker y beneficiarse del aumento de confiabilidad y rendimiento que brinda, sin necesidad de programar con APIs de bajo nivel. @@ -52,7 +52,7 @@ Instalar el service worker de Angular es tan sencillo como [ejecutar un comando Además de registrar el service worker de Angular en el navegador, esto también habilita algunos servicios que se pueden inyectar, los cuales interactúan con el service worker y se pueden usar para controlarlo. Por ejemplo, una aplicación puede solicitar que se le notifique cuando haya una actualización disponible o pedirle al service worker que verifique en el servidor si existen actualizaciones nuevas. -## Antes de comenzar +## Antes de comenzar {#before-you-start} Para aprovechar todas las funciones de los service workers de Angular, usa las versiones más recientes de Angular y de la [Angular CLI](tools/cli). @@ -62,7 +62,7 @@ La razón es que los service workers son bastante potentes, por lo que se debe t Existe una excepción a esta regla: para facilitar el desarrollo local, los navegadores *no* requieren una conexión segura cuando se accede a una aplicación en `localhost`. -### Compatibilidad con navegadores +### Compatibilidad con navegadores {#browser-support} Para beneficiarte del service worker de Angular, tu aplicación debe ejecutarse en un navegador que admita service workers en general. Actualmente, los service workers son compatibles con las versiones más recientes de Chrome, Firefox, Edge, Safari, Opera, UC Browser (versión de Android) y Samsung Internet. @@ -82,7 +82,7 @@ Para evitar ese error, verifica si el service worker de Angular está habilitado Para obtener más información sobre los navegadores preparados para service workers, consulta la página [Can I Use](https://caniuse.com/#feat=serviceworkers) y la [documentación de MDN](https://developer.mozilla.org/es/docs/Web/API/Service_Worker_API). -## Recursos relacionados +## Recursos relacionados {#related-resources} El resto de los artículos de esta sección abordan específicamente la implementación de service workers en Angular. @@ -105,6 +105,6 @@ Para recomendaciones y ejemplos adicionales, consulta: -## Próximo paso +## Próximo paso {#next-step} Para comenzar a usar los service workers de Angular, consulta [Getting Started with service workers](ecosystem/service-workers/getting-started). diff --git a/adev-es/src/content/ecosystem/service-workers/push-notifications.md b/adev-es/src/content/ecosystem/service-workers/push-notifications.md index efacb5bd..47a961ab 100644 --- a/adev-es/src/content/ecosystem/service-workers/push-notifications.md +++ b/adev-es/src/content/ecosystem/service-workers/push-notifications.md @@ -8,7 +8,7 @@ El service worker de Angular permite mostrar notificaciones push y manejar event ÚTIL: Cuando usas el service worker de Angular, las interacciones con notificaciones push se manejan con el servicio `SwPush`. Para conocer más sobre las APIs del navegador involucradas, consulta [Push API](https://developer.mozilla.org/es/docs/Web/API/Push_API) y [Using the Notifications API](https://developer.mozilla.org/es/docs/Web/API/Notifications_API/Using_the_Notifications_API). -## Payload de la notificación +## Payload de la notificación {#notification-payload} Invoca notificaciones push enviando un mensaje con un payload válido. Consulta `SwPush` para obtener orientación. @@ -16,7 +16,7 @@ Consulta `SwPush` para obtener orientación. ÚTIL: En Chrome, puedes probar las notificaciones push sin un backend. Abre DevTools -> Application -> Service Workers y usa el campo `Push` para enviar un payload de notificación JSON. -## Manejo del clic en la notificación +## Manejo del clic en la notificación {#notification-click-handling} El comportamiento predeterminado del evento `notificationclick` es cerrar la notificación y notificar a `SwPush.notificationClicks`. @@ -36,7 +36,7 @@ Esto es especialmente útil cuando no hay clientes abiertos al hacer clic en la } ``` -### Operaciones +### Operaciones {#operations} El service worker de Angular admite las siguientes operaciones: @@ -49,7 +49,7 @@ El service worker de Angular admite las siguientes operaciones: IMPORTANTE: Las URL se resuelven en relación con el scope de registro del service worker.
    Si un elemento `onActionClick` no define una `url`, se usa el scope de registro del service worker. -### Acciones +### Acciones {#actions} Las acciones ofrecen una forma de personalizar cómo puede interactuar la persona usuaria con una notificación. @@ -84,7 +84,7 @@ Además, usando la propiedad `onActionClick` en el objeto `data`, puedes vincula IMPORTANTE: Si una acción no tiene una entrada correspondiente en `onActionClick`, la notificación se cierra y `SwPush.notificationClicks` se notifica en los clientes existentes. -## Más sobre los service workers de Angular +## Más sobre los service workers de Angular {#more-on-angular-service-workers} También podría interesarte lo siguiente: diff --git a/adev-es/src/content/ecosystem/web-workers.en.md b/adev-es/src/content/ecosystem/web-workers.en.md index 9ebd9fcd..74ba7736 100644 --- a/adev-es/src/content/ecosystem/web-workers.en.md +++ b/adev-es/src/content/ecosystem/web-workers.en.md @@ -26,29 +26,26 @@ The command performs the following actions. 1. Adds the following scaffold code to `src/app/app.worker.ts` to receive messages. ```ts {header:"src/app/app.worker.ts"} - - addEventListener('message', ({ data }) => { - const response = `worker response to ${data}`; - postMessage(response); - }); - + addEventListener('message', ({data}) => { + const response = `worker response to ${data}`; + postMessage(response); + }); ``` 1. Adds the following scaffold code to `src/app/app.component.ts` to use the worker. ```ts {header:"src/app/app.component.ts"} - - if (typeof Worker !== 'undefined') { - // Create a new - const worker = new Worker(new URL('./app.worker', import.meta.url)); - worker.onmessage = ({ data }) => { - console.log(`page got message: ${data}`); - }; - worker.postMessage('hello'); - } else { - // Web workers are not supported in this environment. - // You should add a fallback so that your program still executes correctly. - } + if (typeof Worker !== 'undefined') { + // Create a new + const worker = new Worker(new URL('./app.worker', import.meta.url)); + worker.onmessage = ({data}) => { + console.log(`page got message: ${data}`); + }; + worker.postMessage('hello'); + } else { + // Web workers are not supported in this environment. + // You should add a fallback so that your program still executes correctly. + } ``` After you create this initial scaffold, you must refactor your code to use the web worker by sending messages to and from the worker. diff --git a/adev-es/src/content/ecosystem/web-workers.md b/adev-es/src/content/ecosystem/web-workers.md index f92b3144..7b13a2b0 100644 --- a/adev-es/src/content/ecosystem/web-workers.md +++ b/adev-es/src/content/ecosystem/web-workers.md @@ -5,7 +5,7 @@ Las aplicaciones que realizan muchos cálculos, como generar dibujos de diseño CONSEJO: Angular CLI no permite ejecutarse a sí mismo dentro de un web worker. -## Agregar un web worker +## Agregar un web worker {#adding-a-web-worker} Para agregar un web worker a un proyecto existente, usa el comando `ng generate` de Angular CLI. diff --git a/adev-es/src/content/events/v21.md b/adev-es/src/content/events/v21.md index e1f5ace3..675e931a 100644 --- a/adev-es/src/content/events/v21.md +++ b/adev-es/src/content/events/v21.md @@ -4,7 +4,7 @@ ## Release Blog -**Angular v21 is live**: check out the [v21 release blog](http://goo.gle/angular-v21-blog) to learn about all of the amazing new features coming your way. +**Angular v21 is live**: check out the [v21 release blog](https://goo.gle/angular-v21-blog) to learn about all of the amazing new features coming your way. ## Experience the v21 Release @@ -22,16 +22,4 @@ Angular v21 is being delivered to you as a brand new release adventure. With mod - Your first look at Signal Forms, our new streamlined, signal-based approach to forms in Angular - Exciting new details about the Angular Aria package -
    - -
    + diff --git a/adev-es/src/content/events/v22.en.md b/adev-es/src/content/events/v22.en.md new file mode 100644 index 00000000..028563bd --- /dev/null +++ b/adev-es/src/content/events/v22.en.md @@ -0,0 +1,30 @@ +# Angular v22: Rock solid foundation for building what's next for the web + +![A digital illustration styled like a vintage postcard with a thin white border. The top left features the words "Welcome to" written in a cursive, yellow script font. The main focus is a large, brown rocky mountain in the center. Imposed on the mountain face is a 3D white text block reading "Angular V22" along with the Angular logo icon. Dense pine trees in shades of green, yellow, and grey line the foreground at the base of the mountain. A winding path trails up the right side of the mountain, and a few birds fly in the sunset-colored purple and orange sky.](assets/images/v22-event/angular-v22-hero.png {loading: 'eager', fetchpriority: 'high'} 'Angular v22 Hero Image') + +Angular v22 is ready for you to build modern, high-performance web applications. This release introduces key stabilization updates, template enhancements, and API improvements: + +- **Stabilized APIs**: Signal Forms, Asynchronous Signals, and Angular Aria are now stable, offering a production-ready reactive foundation. +- **Template enhancements**: New template features streamline development, improve ergonomics, and enhance code clarity. +- **API improvements**: Core APIs have been updated for better performance, simpler syntax, and more robust typing. +- **Angular AI integration**: Streamlined support and updates for AI-driven development workflows. + +## Key resources + + + + Discover all the major features, community updates, and design decisions behind Angular v22. + + + Review the detailed, commit-by-commit list of new features, bug fixes, and breaking changes. + + + Get instructions on how to update your project to Angular v22. + + + +## Developer event + +[Watch the Angular v22 Developer Event online](https://goo.gle/angular-v22-yt). + + diff --git a/adev-es/src/content/events/v22.md b/adev-es/src/content/events/v22.md new file mode 100644 index 00000000..5e1a831c --- /dev/null +++ b/adev-es/src/content/events/v22.md @@ -0,0 +1,30 @@ +# Angular v22: Una base sólida para construir el futuro de la web + +![Una ilustración digital al estilo de una postal antigua con un delgado borde blanco. La esquina superior izquierda muestra las palabras "Welcome to" escritas en una fuente cursiva de color amarillo. El punto focal principal es una gran montaña rocosa de color marrón en el centro. Sobre la ladera de la montaña se superpone un bloque de texto blanco en 3D que dice "Angular V22" junto con el ícono del logo de Angular. Densos pinos en tonos de verde, amarillo y gris bordean el primer plano en la base de la montaña. Un sendero serpenteante sube por el lado derecho de la montaña, y algunas aves vuelan en el cielo púrpura y naranja del atardecer.](assets/images/v22-event/angular-v22-hero.png {loading: 'eager', fetchpriority: 'high'} 'Angular v22 Hero Image') + +Angular v22 está listo para que construyas aplicaciones web modernas y de alto rendimiento. Esta versión introduce actualizaciones clave de estabilización, mejoras de plantillas y mejoras de API: + +- **APIs estabilizadas**: Signal Forms, Asynchronous Signals y Angular Aria ahora son estables, ofreciendo una base reactiva lista para producción. +- **Mejoras de plantillas**: Nuevas características de plantillas simplifican el desarrollo, mejoran la ergonomía y aumentan la claridad del código. +- **Mejoras de API**: Las APIs principales se actualizaron para lograr mejor rendimiento, sintaxis más simple y tipado más robusto. +- **Integración de Angular AI**: Soporte optimizado y actualizaciones para flujos de trabajo de desarrollo impulsados por IA. + +## Recursos clave + + + + Descubre todas las características principales, actualizaciones de la comunidad y decisiones de diseño detrás de Angular v22. + + + Revisa la lista detallada, commit por commit, de nuevas características, correcciones de errores y cambios disruptivos. + + + Obtén instrucciones sobre cómo actualizar tu proyecto a Angular v22. + + + +## Evento para desarrolladores + +[Mira el Evento para Desarrolladores de Angular v22 en línea](https://goo.gle/angular-v22-yt). + + diff --git a/adev-es/src/content/examples/i18n/readme.md b/adev-es/src/content/examples/i18n/readme.md index a724b587..f72a08f4 100644 --- a/adev-es/src/content/examples/i18n/readme.md +++ b/adev-es/src/content/examples/i18n/readme.md @@ -10,9 +10,9 @@ This sample comes from the Angular documentation's "[Example Angular Internation > See the scripts in `package.json` for an explanation of these commands. -## Run in Stackblitz +## Run in StackBlitz -Stackblitz compiles and runs the English version by default. +StackBlitz compiles and runs the English version by default. To see the example translate to French with Angular i18n: @@ -24,4 +24,4 @@ To see the example translate to French with Angular i18n: } ``` -1. Click the "Fork" button in the stackblitz header. That makes a new copy for you with this change and re-runs the example in French. +1. Click the "Fork" button in the StackBlitz header. That makes a new copy for you with this change and re-runs the example in French. diff --git a/adev-es/src/content/guide/animations/complex-sequences.en.md b/adev-es/src/content/guide/animations/complex-sequences.en.md index 27a69f38..0efe9729 100644 --- a/adev-es/src/content/guide/animations/complex-sequences.en.md +++ b/adev-es/src/content/guide/animations/complex-sequences.en.md @@ -1,6 +1,6 @@ # Complex animation sequences -IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations/enter-and-leave). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. +IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](/guide/animations). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. So far, we've learned simple animations of single HTML elements. Angular also lets you animate coordinated sequences, such as an entire grid or list of elements as they enter and leave a page. @@ -17,12 +17,12 @@ The functions that control complex animation sequences are: ## The query() function -Most complex animations rely on the `query()` function to find child elements and apply animations to them, basic examples of such are: +Most complex animations rely on the `query()` function to find child elements and apply animations to them. Basic examples include: -| Examples | Details | -| :------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `query()` followed by `animate()` | Used to query simple HTML elements and directly apply animations to them. | -| `query()` followed by `animateChild()` | Used to query child elements, which themselves have animations metadata applied to them and trigger such animation \(which would be otherwise be blocked by the current/parent element's animation\). | +| Examples | Details | +| :------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `query()` followed by `animate()` | Used to query simple HTML elements and directly apply animations to them. | +| `query()` followed by `animateChild()` | Used to query child elements, which themselves have animation metadata applied to them and trigger such animations \(which would otherwise be blocked by the current/parent element's animation\). | The first argument of `query()` is a [css selector](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) string which can also contain the following Angular-specific tokens: @@ -43,7 +43,7 @@ You can also see an illustration of this in the animations example \(introduced ## Animate multiple elements using query() and stagger() functions -After having queried child elements via `query()`, the `stagger()` function lets you define a timing gap between each queried item that is animated and thus animates elements with a delay between them. +After querying child elements via `query()`, the `stagger()` function lets you define a timing gap between each item, animating elements with a delay between them. The following example demonstrates how to use the `query()` and `stagger()` functions to animate a list \(of heroes\) adding each in sequence, with a slight delay, from top to bottom. @@ -54,7 +54,7 @@ The following example demonstrates how to use the `query()` and `stagger()` func - Use `stagger()` to delay each animation by 30 milliseconds - Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it - + ## Parallel animation using group() function @@ -67,7 +67,7 @@ HELPFUL: The [`group()`](api/animations/group) function is used to group animati The following example uses [`group()`](api/animations/group)s on both `:enter` and `:leave` for two different timing configurations, thus applying two independent animations to the same element in parallel. - + ## Sequential vs. parallel animations @@ -91,11 +91,11 @@ The heroes list gradually re-enters the page as you delete each letter in the fi The HTML template contains a trigger called `filterAnimation`. - + The `filterAnimation` in the component's decorator contains three transitions. - + The code in this example performs the following tasks: diff --git a/adev-es/src/content/guide/animations/complex-sequences.md b/adev-es/src/content/guide/animations/complex-sequences.md index 16106f19..dcf29b05 100644 --- a/adev-es/src/content/guide/animations/complex-sequences.md +++ b/adev-es/src/content/guide/animations/complex-sequences.md @@ -1,6 +1,6 @@ # Secuencias de animación complejas -IMPORTANTE: El paquete `@angular/animations` ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con `animate.enter` y `animate.leave` para animaciones en todo código nuevo. Aprende más en la nueva [guía de animaciones](guide/animations/enter-and-leave) de entrada y salida. También consulta [Migrando del paquete de Animations de Angular](guide/animations/migration) para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones. +IMPORTANTE: El paquete `@angular/animations` ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con `animate.enter` y `animate.leave` para animaciones en todo código nuevo. Aprende más en la nueva [guía de animaciones](/guide/animations) de entrada y salida. También consulta [Migrando del paquete de Animations de Angular](guide/animations/migration) para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones. Hasta ahora, hemos aprendido animaciones simples de elementos HTML únicos. Angular también te permite animar secuencias coordinadas, como una cuadrícula o lista completa de elementos a medida que entran y salen de una página. @@ -15,7 +15,7 @@ Las funciones que controlan secuencias de animación complejas son: | [`group()`](api/animations/group) | Ejecuta múltiples pasos de animación en paralelo. | | `sequence()` | Ejecuta pasos de animación uno tras otro. | -## La función query() +## La función query() {#the-query-function} La mayoría de las animaciones complejas dependen de la función `query()` para encontrar elementos hijos y aplicarles animaciones, ejemplos básicos de esto son: @@ -37,11 +37,11 @@ El primer argumento de `query()` es una cadena de [selector CSS](https://develop No todos los elementos hijos son realmente considerados como que entran/salen; esto puede, a veces, ser contraintuitivo y confuso. Por favor consulta la [documentación de la API de query](api/animations/query#entering-and-leaving-elements) para más información. -También puedes ver una ilustración de esto en el ejemplo de animaciones (introducido en la [sección de introducción de animaciones](guide/legacy-animations#acerca-de-esta-guía)) bajo la pestaña Querying. +También puedes ver una ilustración de esto en el ejemplo de animaciones (introducido en la [sección de introducción de animaciones](guide/legacy-animations#about-this-guide)) bajo la pestaña Querying. -## Animar múltiples elementos usando las funciones query() y stagger() +## Animar múltiples elementos usando las funciones query() y stagger() {#animate-multiple-elements-using-query-and-stagger-functions} Después de haber consultado elementos hijos a través de `query()`, la función `stagger()` te permite definir un espacio de tiempo entre cada elemento consultado que se anima y, por lo tanto, anima elementos con un retraso entre ellos. @@ -54,9 +54,9 @@ El siguiente ejemplo demuestra cómo usar las funciones `query()` y `stagger()` - Usa `stagger()` para retrasar cada animación por 30 milisegundos - Anima cada elemento en pantalla durante 0.5 segundos usando una curva de easing personalizada definida, simultáneamente desvaneciéndolo y sin transformarlo - + -## Animación paralela usando la función group() +## Animación paralela usando la función group() {#parallel-animation-using-group-function} Has visto cómo agregar un retraso entre cada animación sucesiva. Pero también es posible que desees configurar animaciones que sucedan en paralelo. @@ -67,9 +67,9 @@ Para esto, puedes usar la función de animación [`group()`](api/animations/grou El siguiente ejemplo usa [`group()`](api/animations/group)s en `:enter` y `:leave` para dos configuraciones de tiempo diferentes, aplicando así dos animaciones independientes al mismo elemento en paralelo. - + -## Animaciones secuenciales vs. paralelas +## Animaciones secuenciales vs. paralelas {#sequential-vs-parallel-animations} Las animaciones complejas pueden tener muchas cosas sucediendo a la vez. Pero ¿qué pasa si deseas crear una animación que involucre varias animaciones sucediendo una tras otra? Anteriormente usaste [`group()`](api/animations/group) para ejecutar múltiples animaciones todas al mismo tiempo, en paralelo. @@ -80,7 +80,7 @@ Dentro de `sequence()`, los pasos de animación consisten en llamadas a funcione - Usa `style()` para aplicar los datos de estilo proporcionados inmediatamente. - Usa `animate()` para aplicar datos de estilo durante un intervalo de tiempo dado. -## Ejemplo de animación de filtro +## Ejemplo de animación de filtro {#filter-animation-example} Echa un vistazo a otra animación en la página de ejemplo. Bajo la pestaña Filter/Stagger, ingresa algo de texto en el cuadro de texto **Search Heroes**, como `Magnet` o `tornado`. @@ -91,11 +91,11 @@ La lista de héroes gradualmente vuelve a entrar en la página a medida que elim La plantilla HTML contiene un trigger llamado `filterAnimation`. - + El `filterAnimation` en el decorador del componente contiene tres transiciones. - + El código en este ejemplo realiza las siguientes tareas: @@ -110,7 +110,7 @@ Para cada cambio: - Si hay múltiples elementos entrando o saliendo del DOM, escalona cada animación comenzando desde la parte superior de la página, con un retraso de 50 milisegundos entre cada elemento -## Animando los elementos de una lista que se reordena +## Animando los elementos de una lista que se reordena {#animating-the-items-of-a-reordering-list} Aunque Angular anima correctamente los elementos de listas `*ngFor` de forma predeterminada, no podrá hacerlo si su ordenamiento cambia. Esto se debe a que perderá el rastro de qué elemento es cuál, resultando en animaciones rotas. @@ -119,7 +119,7 @@ Esto asegura que Angular siempre sepa qué elemento es cuál, permitiéndole apl IMPORTANTE: Si necesitas animar los elementos de una lista `*ngFor` y existe la posibilidad de que el orden de dichos elementos cambie durante el tiempo de ejecución, siempre usa una `TrackByFunction`. -## Animaciones y encapsulación de vista de componentes +## Animaciones y encapsulación de vista de componentes {#animations-and-component-view-encapsulation} Las animaciones de Angular se basan en la estructura DOM de los componentes y no tienen en cuenta directamente la [encapsulación de vista](guide/components/styling#style-scoping), esto significa que los componentes que usan `ViewEncapsulation.Emulated` se comportan exactamente como si estuvieran usando `ViewEncapsulation.None` (`ViewEncapsulation.ShadowDom` se comporta de manera diferente como discutiremos en breve). @@ -127,12 +127,12 @@ Por ejemplo, si la función `query()` (que verás más en el resto de la guía d Por otro lado, el `ViewEncapsulation.ShadowDom` y `ViewEncapsulation.ExperimentalIsolatedShadowDom` cambian la estructura DOM del componente "ocultando" elementos DOM dentro de elementos [`ShadowRoot`](https://developer.mozilla.org/docs/Web/API/ShadowRoot). Tales manipulaciones DOM previenen que algunas de las implementaciones de animaciones funcionen correctamente ya que se basa en estructuras DOM simples y no tiene en cuenta elementos `ShadowRoot`. Por lo tanto, se aconseja evitar aplicar animaciones a vistas que incorporan componentes usando la encapsulación de vista ShadowDom. -## Resumen de secuencia de animación +## Resumen de secuencia de animación {#animation-sequence-summary} Las funciones de Angular para animar múltiples elementos comienzan con `query()` para encontrar elementos internos; por ejemplo, recopilando todas las imágenes dentro de un `
    `. Las funciones restantes, `stagger()`, [`group()`](api/animations/group), y `sequence()`, aplican cascadas o te permiten controlar cómo se aplican múltiples pasos de animación. -## Más sobre animaciones de Angular +## Más sobre animaciones de Angular {#more-on-angular-animations} También puede que te interese lo siguiente: diff --git a/adev-es/src/content/guide/animations/css.en.md b/adev-es/src/content/guide/animations/css.en.md index 0e68cf25..57816430 100644 --- a/adev-es/src/content/guide/animations/css.en.md +++ b/adev-es/src/content/guide/animations/css.en.md @@ -4,7 +4,7 @@ CSS offers a robust set of tools for you to create beautiful and engaging animat ## How to write animations in native CSS -If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here's a few of them: +If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here are a few of them: [MDN's CSS Animations guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) [W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp) [The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/) @@ -14,13 +14,13 @@ and a couple of videos: [Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw) [Net Ninja CSS Animation Tutorial Playlist](https://www.youtube.com/watch?v=jgw82b5Y2MU&list=PL4cUxeGkcC9iGYgmEd2dm3zAKzyCGDtM5) -Check some of these various guides and tutorials out, and then come back to this guide. +Check out some of these guides and tutorials, then come back to this guide. ## Creating Reusable Animations You can create reusable animations that can be shared across your application using `@keyframes`. Define keyframe animations in a shared CSS file, and you'll be able to re-use those keyframe animations wherever you want within your application. - + Adding the class `animated-class` to an element would trigger the animation on that element. @@ -28,9 +28,9 @@ Adding the class `animated-class` to an element would trigger the animation on t ### Animating State and Styles -You may want to animate between two different states, for example when an element is opened or closed. You can accomplish this by using CSS classes either using a keyframe animation or transition styling. +You may want to animate between two different states, for example when an element is opened or closed. You can accomplish this by using CSS classes, either with a keyframe animation or transition styling. - + Triggering the `open` or `closed` state is done by toggling classes on the element in your component. You can find examples of how to do this in our [template guide](guide/templates/binding#css-class-and-style-property-bindings). @@ -38,56 +38,56 @@ You can see similar examples in the template guide for [animating styles directl ### Transitions, Timing, and Easing -Animating often requires adjusting timing, delays and easeing behaviors. This can be done using several css properties or shorthand properties. +Animating often requires adjusting timing, delays, and easing behaviors. This can be done using several css properties or shorthand properties. Specify `animation-duration`, `animation-delay`, and `animation-timing-function` for a keyframe animation in CSS, or alternatively use the `animation` shorthand property. - + Similarly, you can use `transition-duration`, `transition-delay`, and `transition-timing-function` and the `transition` shorthand for animations that are not using `@keyframes`. - + ### Triggering an Animation Animations can be triggered by toggling CSS styles or classes. Once a class is present on an element, the animation will occur. Removing the class will revert the element back to whatever CSS is defined for that element. Here's an example: - - - - + + + + ## Transition and Triggers ### Animating Auto Height -You can use css-grid to animate to auto height. +You can use CSS Grid to animate to auto height. - - - - + + + + -If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and (this tutorial)[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/] for more information. +If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and [this tutorial](https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/) for more information. ### Animate entering and leaving a view You can create animations for when an item enters a view or leaves a view. Let's start by looking at how to animate an element entering a view. We'll do this with `animate.enter`, which will apply animation classes when an element enters the view. - - - - + + + + Animating an element when it leaves the view is similar to animating when entering a view. Use `animate.leave` to specify which CSS classes to apply when the element leaves the view. - - - - + + + + For more information on `animate.enter` and `animate.leave`, see the [Enter and Leave animations guide](guide/animations). @@ -96,10 +96,10 @@ For more information on `animate.enter` and `animate.leave`, see the [Enter and Animating on increment and decrement is a common pattern in applications. Here's an example of how you can accomplish that behavior. - - - - + + + + ### Disabling an animation or all animations @@ -147,10 +147,10 @@ Animations are often more complicated than just a simple fade in or fade out. Yo One common effect is to stagger the animations of each item in a list to create a cascade effect. This can be accomplished by utilizing `animation-delay` or `transition-delay`. Here is an example of what that CSS might look like. - - - - + + + + ### Parallel Animations @@ -159,7 +159,9 @@ You can apply multiple animations to an element at once using the `animation` sh ```css .target-element { - animation: rotate 3s, fade-in 2s; + animation: + rotate 3s, + fade-in 2s; } ``` @@ -169,10 +171,10 @@ In this example, the `rotate` and `fade-in` animations fire at the same time, bu Items in a `@for` loop will be removed and re-added, which will fire off animations using `@starting-styles` for entry animations. Alternatively, you can use `animate.enter` for this same behavior. Use `animate.leave` to animate elements as they are removed, as seen in the example below. - - - - + + + + ## Programmatic control of animations diff --git a/adev-es/src/content/guide/animations/css.md b/adev-es/src/content/guide/animations/css.md index 20f5c3e9..763de140 100644 --- a/adev-es/src/content/guide/animations/css.md +++ b/adev-es/src/content/guide/animations/css.md @@ -2,7 +2,7 @@ CSS ofrece un conjunto robusto de herramientas para crear animaciones hermosas y atractivas dentro de tu aplicación. -## Cómo escribir animaciones en CSS nativo +## Cómo escribir animaciones en CSS nativo {#how-to-write-animations-in-native-css} Si nunca has escrito animaciones en CSS nativo, hay varias guías excelentes para comenzar. Aquí hay algunas de ellas: [Guía de animaciones CSS de MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) @@ -16,93 +16,93 @@ y un par de videos: Consulta algunas de estas diversas guías y tutoriales, y luego regresa a esta guía. -## Creando animaciones reutilizables +## Creando animaciones reutilizables {#creating-reusable-animations} Puedes crear animaciones reutilizables que se pueden compartir en toda tu aplicación usando `@keyframes`. Define animaciones de keyframes en un archivo CSS compartido, y podrás reutilizar esas animaciones de keyframes donde quieras dentro de tu aplicación. - + Agregar la clase `animated-class` a un elemento activaría la animación en ese elemento. -## Animando una transición +## Animando una transición {#animating-a-transition} -### Animando estado y estilos +### Animando estado y estilos {#animating-state-and-styles} Es posible que desees animar entre dos estados diferentes, por ejemplo cuando un elemento está abierto o cerrado. Puedes lograr esto usando clases CSS ya sea mediante una animación de keyframe o estilo de transición. - + Disparar el estado `open` o `closed` se hace alternando clases en el elemento en tu componente. Puedes encontrar ejemplos de cómo hacer esto en nuestra [guía de plantillas](guide/templates/binding#css-class-and-style-property-bindings). Puedes ver ejemplos similares en la guía de plantillas para [animar estilos directamente](guide/templates/binding#css-style-properties). -### Transiciones, tiempo y easing +### Transiciones, tiempo y easing {#transitions-timing-and-easing} Animar a menudo requiere ajustar comportamientos de tiempo, retrasos y easing. Esto se puede hacer usando varias propiedades CSS o propiedades abreviadas. Especifica `animation-duration`, `animation-delay` y `animation-timing-function` para una animación de keyframe en CSS, o alternativamente usa la propiedad abreviada `animation`. - + De manera similar, puedes usar `transition-duration`, `transition-delay` y `transition-timing-function` y la abreviación `transition` para animaciones que no están usando `@keyframes`. - + -### Disparando una animación +### Disparando una animación {#triggering-an-animation} Las animaciones se pueden disparar alternando estilos o clases CSS. Una vez que una clase está presente en un elemento, la animación ocurrirá. Eliminar la clase revertirá el elemento a cualquier CSS que esté definido para ese elemento. Aquí hay un ejemplo: - - - - + + + + -## Transiciones y triggers +## Transiciones y triggers {#transition-and-triggers} -### Animando altura automática +### Animando altura automática {#animating-auto-height} Puedes usar css-grid para animar a altura automática. - - - - + + + + -Si no tienes que preocuparte por soportar todos los navegadores, también puedes revisar `calc-size()`, que es la verdadera solución para animar altura automática. Consulta [la documentación de MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) y (este tutorial)[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/] para más información. +Si no tienes que preocuparte por soportar todos los navegadores, también puedes revisar `calc-size()`, que es la verdadera solución para animar altura automática. Consulta [la documentación de MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) y [este tutorial](https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/) para más información. -### Animar entrada y salida de una vista +### Animar entrada y salida de una vista {#animate-entering-and-leaving-a-view} Puedes crear animaciones para cuando un elemento entra en una vista o sale de una vista. Comencemos viendo cómo animar un elemento que entra en una vista. Haremos esto con `animate.enter`, que aplicará clases de animación cuando un elemento entre en la vista. - - - - + + + + Animar un elemento cuando sale de la vista es similar a animar cuando entra en una vista. Usa `animate.leave` para especificar qué clases CSS aplicar cuando el elemento sale de la vista. - - - - + + + + Para más información sobre `animate.enter` y `animate.leave`, consulta la [guía de animaciones de entrada y salida](guide/animations). -### Animando incremento y decremento +### Animando incremento y decremento {#animating-increment-and-decrement} Animar en incremento y decremento es un patrón común en aplicaciones. Aquí hay un ejemplo de cómo puedes lograr ese comportamiento. - - - - + + + + -### Deshabilitando una animación o todas las animaciones +### Deshabilitando una animación o todas las animaciones {#disabling-an-animation-or-all-animations} Si deseas deshabilitar las animaciones que has especificado, tienes múltiples opciones. @@ -121,7 +121,7 @@ Aplicar esta clase a un elemento previene que cualquier animación se dispare en 3. Prevenir la adición de clases de animación programáticamente -### Callbacks de animación +### Callbacks de animación {#animation-callbacks} Si tienes acciones que te gustaría ejecutar en ciertos puntos durante las animaciones, hay varios eventos disponibles que puedes escuchar. Aquí hay algunos de ellos. @@ -139,21 +139,21 @@ La API de Web Animations tiene mucha funcionalidad adicional. [Echa un vistazo a NOTA: Ten en cuenta los problemas de propagación con estos callbacks. Si estás animando hijos y padres, los eventos se propagan desde los hijos hacia los padres. Considera detener la propagación o examinar más detalles dentro del evento para determinar si estás respondiendo al objetivo de evento deseado en lugar de un evento que se propaga desde un nodo hijo. Puedes examinar la propiedad `animationname` o las propiedades que están siendo transicionadas para verificar que tienes los nodos correctos. -## Secuencias complejas +## Secuencias complejas {#complex-sequences} Las animaciones son a menudo más complicadas que solo un simple fade in o fade out. Puedes tener muchas secuencias complicadas de animaciones que desees ejecutar. Echemos un vistazo a algunos de esos posibles escenarios. -### Escalonando animaciones en una lista +### Escalonando animaciones en una lista {#staggering-animations-in-a-list} Un efecto común es escalonar las animaciones de cada elemento en una lista para crear un efecto en cascada. Esto se puede lograr utilizando `animation-delay` o `transition-delay`. Aquí hay un ejemplo de cómo podría verse ese CSS. - - - - + + + + -### Animaciones paralelas +### Animaciones paralelas {#parallel-animations} Puedes aplicar múltiples animaciones a un elemento a la vez usando la propiedad abreviada `animation`. Cada una puede tener sus propias duraciones y retrasos. Esto te permite componer animaciones juntas y crear efectos complicados. @@ -165,21 +165,21 @@ Puedes aplicar múltiples animaciones a un elemento a la vez usando la propiedad En este ejemplo, las animaciones `rotate` y `fade-in` se disparan al mismo tiempo, pero tienen diferentes duraciones. -### Animando los elementos de una lista que se reordena +### Animando los elementos de una lista que se reordena {#animating-the-items-of-a-reordering-list} Los elementos en un bucle `@for` serán eliminados y re-agregados, lo que disparará animaciones usando `@starting-styles` para animaciones de entrada. Alternativamente, puedes usar `animate.enter` para este mismo comportamiento. Usa `animate.leave` para animar elementos a medida que se eliminan, como se ve en el ejemplo a continuación. - - - - + + + + -## Control programático de animaciones +## Control programático de animaciones {#programmatic-control-of-animations} Puedes obtener animaciones de un elemento directamente usando [`Element.getAnimations()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations). Esto devuelve un array de cada [`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation) en ese elemento. Puedes usar la API de `Animation` para hacer mucho más de lo que podías con lo que ofrecía el `AnimationPlayer` del paquete de animaciones. Desde aquí puedes `cancel()`, `play()`, `pause()`, `reverse()` y mucho más. Esta API nativa debería proporcionar todo lo que necesitas para controlar tus animaciones. -## Más sobre animaciones de Angular +## Más sobre animaciones de Angular {#more-on-angular-animations} También puede que te interese lo siguiente: diff --git a/adev-es/src/content/guide/animations/enter-and-leave.en.md b/adev-es/src/content/guide/animations/enter-and-leave.en.md index 8ab48087..7817950e 100644 --- a/adev-es/src/content/guide/animations/enter-and-leave.en.md +++ b/adev-es/src/content/guide/animations/enter-and-leave.en.md @@ -1,13 +1,13 @@ # Animating your applications with `animate.enter` and `animate.leave` -Well-designed animations can make your application more fun and straightforward to use, but they aren't just cosmetic. -Animations can improve your application and user experience in a number of ways: +Well-designed animations can make your application more intuitive and engaging, but they aren't just cosmetic. +Animations can improve your application and the user experience in a number of ways: - Without animations, web page transitions can seem abrupt and jarring - Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions - Good animations can smoothly direct the user's attention throughout a workflow -Angular provides `animate.enter` and `animate.leave` to animate your application's elements. These two features apply enter and leave CSS classes at the appropriate times or call functions to apply animations from third party libraries. `animate.enter` and `animate.leave` are not directives. They are special API supported directly by the Angular compiler. They can be used on elements directly and can also be used as a host binding. +Angular provides `animate.enter` and `animate.leave` to animate your application's elements. These two features apply enter and leave CSS classes at the appropriate times or call functions to apply animations from third party libraries. `animate.enter` and `animate.leave` are not directives. They are special API supported directly by the Angular compiler. They can be used directly on elements and also as a host binding. ## `animate.enter` @@ -19,7 +19,7 @@ You can use `animate.enter` to animate elements as they _enter_ the DOM. You can -When the animation completes, Angular removes the class or classes that you specified in `animate.enter` from the DOM. Animation classes are only be present while the animation is active. +When the animation completes, Angular removes the class or classes that you specified in `animate.enter` from the DOM. Animation classes are only present while the animation is active. NOTE: When using multiple keyframe animations or transition properties on an element, Angular removes all classes only _after_ the longest animation has completed. @@ -45,7 +45,7 @@ You can use `animate.leave` to animate elements as they _leave_ the DOM. You can When the animation completes, Angular automatically removes the animated element from the DOM. -NOTE: When using multiple keyframe animations or transition properties on a an element, Angular waits to remove the element only _after_ the longest of those animations has completed. +NOTE: When using multiple keyframe animations or transition properties on an element, Angular waits to remove the element only _after_ the longest of those animations has completed. `animate.leave` can also be used with signals, and other bindings. You can use `animate.leave` with a single class or multiple classes. Either specify it as a simple string with spaces or a string array. @@ -55,6 +55,16 @@ NOTE: When using multiple keyframe animations or transition properties on a an e +### Element removal order + +There is some nuance to how `animate.leave` animations are run and when an animation will occur. `animate.leave` works if it is placed on the element that is being removed, and if `animate.leave` is placed on an element that is a _descendent_ of the element being removed, those child animations will happen _before_ the parent node is removed from the DOM. This ensures that you can confidently animate away child elements without the parent node disappearing prematurely. + + + + + + + ## Event Bindings, Functions, and Third-party Libraries Both `animate.enter` and `animate.leave` support event binding syntax that allows for function calls. You can use this syntax to call a function in your component code or utilize third-party animation libraries, like [GSAP](https://gsap.com/), [anime.js](https://animejs.com/), or any other JavaScript animation library. @@ -86,7 +96,7 @@ TestBed provides built-in support for enabling or disabling animations in your t If you want to test that the animations are animating in a browser test, for example an end-to-end test, you can configure TestBed to enable animations by specifying `animationsEnabled: true` in your test configuration. ```typescript - TestBed.configureTestingModule({animationsEnabled: true}); +TestBed.configureTestingModule({animationsEnabled: true}); ``` This will configure animations in your test environment to behave normally. diff --git a/adev-es/src/content/guide/animations/enter-and-leave.md b/adev-es/src/content/guide/animations/enter-and-leave.md index 2eea8243..40118c4a 100644 --- a/adev-es/src/content/guide/animations/enter-and-leave.md +++ b/adev-es/src/content/guide/animations/enter-and-leave.md @@ -55,7 +55,17 @@ NOTA: Cuando se usan múltiples animaciones de keyframes o propiedades de transi -## Enlaces de eventos, funciones y bibliotecas de terceros +### Orden de eliminación de elementos {#element-removal-order} + +Hay cierta sutileza en cómo se ejecutan las animaciones de `animate.leave` y cuándo ocurrirá una animación. `animate.leave` funciona si se coloca en el elemento que se está eliminando, y si `animate.leave` se coloca en un elemento que es _descendiente_ del elemento que se está eliminando, esas animaciones hijas ocurrirán _antes_ de que el nodo padre sea eliminado del DOM. Esto garantiza que puedas animar con confianza los elementos hijos sin que el nodo padre desaparezca prematuramente. + + + + + + + +## Enlaces de eventos, funciones y bibliotecas de terceros {#event-bindings-functions-and-third-party-libraries} Tanto `animate.enter` como `animate.leave` soportan sintaxis de enlace de eventos que permite llamadas a funciones. Puedes usar esta sintaxis para llamar a una función en el código de tu componente o utilizar bibliotecas de animación de terceros, como [GSAP](https://gsap.com/), [anime.js](https://animejs.com/), o cualquier otra biblioteca de animación JavaScript. @@ -75,11 +85,11 @@ Si no llamas a `animationComplete()` cuando usas `animate.leave`, Angular llama { provide: MAX_ANIMATION_TIMEOUT, useValue: 6000 } ``` -## Compatibilidad con las animaciones heredadas de Angular +## Compatibilidad con las animaciones heredadas de Angular {#compatibility-with-legacy-angular-animations} No puedes usar animaciones heredadas junto con `animate.enter` y `animate.leave` dentro del mismo componente. Hacerlo provocaría que las clases de entrada permanezcan en el elemento o que los nodos que salen no se eliminen. Fuera de eso, está bien usar tanto las animaciones heredadas como las nuevas animaciones `animate.enter` y `animate.leave` dentro de la misma _aplicación_. La única excepción es la proyección de contenido. Si estás proyectando contenido desde un componente con animaciones heredadas hacia otro componente que usa `animate.enter` o `animate.leave`, o viceversa, esto producirá el mismo comportamiento que si se usaran juntas en el mismo componente. Esto no es compatible. -## Pruebas +## Pruebas {#testing} TestBed proporciona soporte integrado para habilitar o deshabilitar animaciones en tu entorno de prueba. Las animaciones CSS requieren un navegador para ejecutarse, y muchas de las APIs no están disponibles en un entorno de prueba. Por defecto, TestBed deshabilita las animaciones para ti en tus entornos de prueba. @@ -93,7 +103,7 @@ Esto configurará las animaciones en tu entorno de prueba para que se comporten NOTA: Algunos entornos de prueba no emiten eventos de animación como `animationstart`, `animationend` y sus equivalentes de eventos de transición. -## Más sobre animaciones de Angular +## Más sobre animaciones de Angular {#more-on-angular-animations} También puede que te interese lo siguiente: diff --git a/adev-es/src/content/guide/animations/migration.en.md b/adev-es/src/content/guide/animations/migration.en.md index 60890a17..7aa4258b 100644 --- a/adev-es/src/content/guide/animations/migration.en.md +++ b/adev-es/src/content/guide/animations/migration.en.md @@ -4,7 +4,7 @@ The `@angular/animations` package is deprecated as of v20.2, which also introduc ## How to write animations in native CSS -If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here's a few of them: +If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here are a few of them: [MDN's CSS Animations guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) [W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp) [The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/) @@ -14,7 +14,7 @@ and a couple of videos: [Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw) [Net Ninja CSS Animation Tutorial Playlist](https://www.youtube.com/watch?v=jgw82b5Y2MU&list=PL4cUxeGkcC9iGYgmEd2dm3zAKzyCGDtM5) -Check some of these various guides and tutorials out, and then come back to this guide. +Check out some of these guides and tutorials, then come back to this guide. ## Creating Reusable Animations @@ -22,11 +22,11 @@ Just like with the animations package, you can create reusable animations that c #### With Animations Package - + #### With Native CSS - + Adding the class `animated-class` to an element would trigger the animation on that element. @@ -38,13 +38,13 @@ The animations package allowed you to define various states using the [`state()` #### With Animations Package - + -This same behavior can be accomplished natively by using CSS classes either using a keyframe animation or transition styling. +This same behavior can be accomplished natively by using CSS classes, either with a keyframe animation or transition styling. #### With Native CSS - + Triggering the `open` or `closed` state is done by toggling classes on the element in your component. You can find examples of how to do this in our [template guide](guide/templates/binding#css-class-and-style-property-bindings). @@ -56,11 +56,11 @@ The animations package `animate()` function allows for providing timing, like du Specify `animation-duration`, `animation-delay`, and `animation-timing-function` for a keyframe animation in CSS, or alternatively use the `animation` shorthand property. - + Similarly, you can use `transition-duration`, `transition-delay`, and `transition-timing-function` and the `transition` shorthand for animations that are not using `@keyframes`. - + ### Triggering an Animation @@ -69,17 +69,17 @@ The animations package required specifying triggers using the `trigger()` functi #### With Animations Package - - - + + + #### With Native CSS - - - - + + + + ## Transition and Triggers @@ -97,19 +97,19 @@ The animations package offers the ability to animate things that have been histo #### With Animations Package - - - + + + -You can use css-grid to animate to auto height. +You can use CSS Grid to animate to auto height. #### With Native CSS - - - - + + + + If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and (this tutorial)[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/] for more information. @@ -121,29 +121,25 @@ The animations package offered the previously mentioned pattern matching for ent #### With Animations Package - - - + + + -Here's how the same thing can be accomplished without the animations package using `animate.enter`. - #### With Native CSS - - - - + + + + -Use `animate.leave` to animate elements as they leave the view, which will apply the specified CSS classes to the element as it leaves the view. - #### With Native CSS - - - - + + + + For more information on `animate.enter` and `animate.leave`, see the [Enter and Leave animations guide](guide/animations). @@ -155,17 +151,17 @@ Along with the aforementioned `:enter` and `:leave`, there's also `:increment` a #### With Animations Package - - - + + + #### With Native CSS - - - - + + + + ### Parent / Child Animations @@ -226,17 +222,17 @@ The `stagger()` function allowed you to delay the animation of each item in a li #### With Animations Package - - - + + + #### With Native CSS - - - - + + + + ### Parallel Animations @@ -245,7 +241,9 @@ The animations package has a `group()` function to play multiple animations at t ```css .target-element { - animation: rotate 3s, fade-in 2s; + animation: + rotate 3s, + fade-in 2s; } ``` @@ -255,20 +253,20 @@ In this example, the `rotate` and `fade-in` animations fire at the same time. Items reordering in a list works out of the box using the previously described techniques. No additional special work is required. Items in a `@for` loop will be removed and re-added properly, which will fire off animations using `@starting-styles` for entry animations. Alternatively, you can use `animate.enter` for this same behavior. Use `animate.leave` to animate elements as they are removed, as seen in the example above. -#### With Animations Package< +#### With Animations Package - - - + + + #### With Native CSS - - - - + + + + ## Migrating usages of AnimationPlayer diff --git a/adev-es/src/content/guide/animations/migration.md b/adev-es/src/content/guide/animations/migration.md index 024bde84..8329d3d3 100644 --- a/adev-es/src/content/guide/animations/migration.md +++ b/adev-es/src/content/guide/animations/migration.md @@ -2,7 +2,7 @@ El paquete `@angular/animations` está deprecado a partir de la v20.2, que también introdujo las nuevas características `animate.enter` y `animate.leave` para agregar animaciones a tu aplicación. Usando estas nuevas características, puedes reemplazar todas las animaciones basadas en `@angular/animations` con CSS puro o bibliotecas de animación JS. Eliminar `@angular/animations` de tu aplicación puede reducir significativamente el tamaño de tu bundle de JavaScript. Las animaciones CSS nativas generalmente ofrecen rendimiento superior, ya que pueden beneficiarse de la aceleración por hardware. Esta guía te acompaña a través del proceso de refactorizar tu código de `@angular/animations` a animaciones CSS nativas. -## Cómo escribir animaciones en CSS nativo +## Cómo escribir animaciones en CSS nativo {#how-to-write-animations-in-native-css} Si nunca has escrito animaciones en CSS nativo, hay varias guías excelentes para comenzar. Aquí hay algunas de ellas: [Guía de animaciones CSS de MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) @@ -16,163 +16,163 @@ y un par de videos: Consulta algunas de estas diversas guías y tutoriales, y luego regresa a esta guía. -## Creando animaciones reutilizables +## Creando animaciones reutilizables {#creating-reusable-animations} Al igual que con el paquete de animaciones, puedes crear animaciones reutilizables que se pueden compartir en toda tu aplicación. La versión del paquete de animaciones de esto te hacía usar la función `animation()` en un archivo TypeScript compartido. La versión CSS nativa de esto es similar, pero vive en un archivo CSS compartido. -#### Con el paquete de Aninaciones +#### Con el paquete de Aninaciones {#with-animations-package} - + -#### Con CSS nativo +#### Con CSS nativo {#with-native-css} - + Agregar la clase `animated-class` a un elemento activaría la animación en ese elemento. -## Animando una transición +## Animando una transición {#animating-a-transition} -### Animando estado y estilos +### Animando estado y estilos {#animating-state-and-styles} El paquete de animaciones te permitía definir varios estados usando la función [`state()`](api/animations/state) dentro de un componente. Los ejemplos podrían ser un estado `open` o `closed` que contiene los estilos para cada estado respectivo dentro de la definición. Por ejemplo: -#### Con el paquete de Aninaciones +#### Con el paquete de Aninaciones {#with-animations-package-1} - + Este mismo comportamiento se puede lograr nativamente usando clases CSS ya sea mediante una animación de keyframe o estilo de transición. -#### Con CSS nativo +#### Con CSS nativo {#with-native-css-1} - + Disparar el estado `open` o `closed` se hace alternando clases en el elemento en tu componente. Puedes encontrar ejemplos de cómo hacer esto en nuestra [guía de plantillas](guide/templates/binding#css-class-and-style-property-bindings). Puedes ver ejemplos similares en la guía de plantillas para [animar estilos directamente](guide/templates/binding#css-style-properties). -### Transiciones, tiempo y easing +### Transiciones, tiempo y easing {#transitions-timing-and-easing} La función `animate()` del paquete de animaciones permite proporcionar tiempo, como duración, retrasos y easing. Esto se puede hacer nativamente con CSS usando varias propiedades CSS o propiedades abreviadas. Especifica `animation-duration`, `animation-delay` y `animation-timing-function` para una animación de keyframe en CSS, o alternativamente usa la propiedad abreviada `animation`. - + De manera similar, puedes usar `transition-duration`, `transition-delay` y `transition-timing-function` y la abreviación `transition` para animaciones que no están usando `@keyframes`. - + -### Disparando una animación +### Disparando una animación {#triggering-an-animation} El paquete de animaciones requería especificar triggers usando la función `trigger()` y anidar todos tus estados dentro de ella. Con CSS nativo, esto es innecesario. Las animaciones se pueden disparar alternando estilos o clases CSS. Una vez que una clase está presente en un elemento, la animación ocurrirá. Eliminar la clase revertirá el elemento a cualquier CSS que esté definido para ese elemento. Esto resulta en significativamente menos código para hacer la misma animación. Aquí hay un ejemplo: -#### Con el paquete de Aninaciones +#### Con el paquete de Aninaciones {#with-animations-package-2} - - - + + + -#### Con CSS nativo +#### Con CSS nativo {#with-native-css-2} - - - - + + + + -## Transiciones y triggers +## Transiciones y triggers {#transition-and-triggers} -### Coincidencia de estado predefinido y comodines +### Coincidencia de estado predefinido y comodines {#predefined-state-and-wildcard-matching} El paquete de animaciones ofrece la capacidad de hacer coincidir tus estados definidos con una transición mediante cadenas. Por ejemplo, animar de open a closed sería `open => closed`. Puedes usar comodines para hacer coincidir cualquier estado con un estado objetivo, como `* => closed` y la palabra clave `void` se puede usar para estados de entrada y salida. Por ejemplo: `* => void` para cuando un elemento sale de una vista o `void => *` para cuando el elemento entra en una vista. Estos patrones de coincidencia de estado no se necesitan en absoluto cuando se anima con CSS directamente. Puedes gestionar qué transiciones y animaciones `@keyframes` se aplican basándote en las clases que establezcas y/o los estilos que establezcas en los elementos. También puedes agregar `@starting-style` para controlar cómo se ve el elemento al entrar inmediatamente al DOM. -### Cálculo automático de propiedades con comodines +### Cálculo automático de propiedades con comodines {#automatic-property-calculation-with-wildcards} El paquete de animaciones ofrece la capacidad de animar cosas que han sido históricamente difíciles de animar, como animar una altura establecida a `height: auto`. Ahora también puedes hacer esto con CSS puro. -#### Con el paquete de Animaciones +#### Con el paquete de Animaciones {#with-animations-package-3} - - - + + + Puedes usar css-grid para animar a altura automática. -#### Con CSS nativo +#### Con CSS nativo {#with-native-css-3} - - - - + + + + Si no tienes que preocuparte por soportar todos los navegadores, también puedes revisar `calc-size()`, que es la verdadera solución para animar altura automática. Consulta [la documentación de MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) y (este tutorial)[https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/] para más información. -### Animar entrada y salida de una vista +### Animar entrada y salida de una vista {#animate-entering-and-leaving-a-view} El paquete de animaciones ofrecía el patrón de coincidencia mencionado anteriormente para entrada y salida, pero también incluía los alias abreviados de `:enter` y `:leave`. -#### Con el paquete de Ani +#### Con el paquete de Ani {#with-animations-package-4} - - - + + + Aquí está cómo se puede lograr lo mismo sin el paquete de animaciones usando `animate.enter`. -#### Con CSS nativo +#### Con CSS nativo {#with-native-css-4} - - - - + + + + Usa `animate.leave` para animar elementos a medida que salen de la vista, lo que aplicará las clases CSS especificadas al elemento a medida que sale de la vista. -#### Con CSS nativo +#### Con CSS nativo {#with-native-css-5} - - - - + + + + Para más información sobre `animate.enter` y `animate.leave`, consulta la [guía de animaciones de entrada y salida](guide/animations). -### Animando incremento y decremento +### Animando incremento y decremento {#animating-increment-and-decrement} Junto con los mencionados `:enter` y `:leave`, también está `:increment` y `:decrement`. También puedes animar estos agregando y eliminando clases. A diferencia de los alias integrados del paquete de animaciones, no hay aplicación automática de clases cuando los valores suben o bajan. Puedes aplicar las clases apropiadas programáticamente. Aquí hay un ejemplo: -#### Con el paquete de Aninaciones +#### Con el paquete de Aninaciones {#with-animations-package-5} - - - + + + -#### Con CSS nativo +#### Con CSS nativo {#with-native-css-6} - - - - + + + + -### Animaciones padre / hijo +### Animaciones padre / hijo {#parent--child-animations} A diferencia del paquete de animaciones, cuando se especifican múltiples animaciones dentro de un componente dado, ninguna animación tiene prioridad sobre otra y nada bloquea que ninguna animación se dispare. Cualquier secuenciación de animaciones tendría que ser manejada por tu definición de tu animación CSS, usando retraso de animation / transition, y/o usando `animationend` o `transitionend` para manejar la adición del siguiente CSS a animar. -### Deshabilitando una animación o todas las animaciones +### Deshabilitando una animación o todas las animaciones {#disabling-an-animation-or-all-animations} Con animaciones CSS nativas, si deseas deshabilitar las animaciones que has especificado, tienes múltiples opciones. @@ -191,7 +191,7 @@ Aplicar esta clase a un elemento previene que cualquier animación se dispare en 3. Prevenir la adición de clases de animación programáticamente -### Callbacks de animación +### Callbacks de animación {#animation-callbacks} El paquete de animaciones exponía callbacks para que uses en el caso de que desees hacer algo cuando la animación haya terminado. Las animaciones CSS nativas también tienen estos callbacks. @@ -209,11 +209,11 @@ La API de Web Animations tiene mucha funcionalidad adicional. [Echa un vistazo a NOTA: Ten en cuenta los problemas de propagación con estos callbacks. Si estás animando hijos y padres, los eventos se propagan desde los hijos hacia los padres. Considera detener la propagación o examinar más detalles dentro del evento para determinar si estás respondiendo al objetivo de evento deseado en lugar de un evento que se propaga desde un nodo hijo. Puedes examinar la propiedad `animationname` o las propiedades que están siendo transicionadas para verificar que tienes los nodos correctos. -## Secuencias complejas +## Secuencias complejas {#complex-sequences} El paquete de animaciones tiene funcionalidad integrada para crear secuencias complejas. Estas secuencias son totalmente posibles sin el paquete de animaciones. -### Dirigirse a elementos específicos +### Dirigirse a elementos específicos {#targeting-specific-elements} En el paquete de animaciones, podías dirigirte a elementos específicos usando la función `query()` para encontrar elementos específicos por un nombre de clase CSS, similar a [`document.querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). Esto es innecesario en un mundo de animación CSS nativa. En su lugar, puedes usar tus selectores CSS para dirigirte a sub-clases y aplicar cualquier `transform` o `animation` deseada. @@ -223,23 +223,23 @@ Para alternar clases para nodos hijos dentro de una plantilla, puedes usar enlac La función `stagger()` te permitía retrasar la animación de cada elemento en una lista de elementos por un tiempo especificado para crear un efecto en cascada. Puedes replicar este comportamiento en CSS nativo utilizando `animation-delay` o `transition-delay`. Aquí hay un ejemplo de cómo podría verse ese CSS. -#### Con el paquete de Aninaciones +#### Con el paquete de Aninaciones {#with-animations-package-6} - - - + + + -#### Con CSS nativo +#### Con CSS nativo {#with-native-css-7} - - - - + + + + -### Animaciones paralelas +### Animaciones paralelas {#parallel-animations} El paquete de animaciones tiene una función `group()` para reproducir múltiples animaciones al mismo tiempo. En CSS, tienes control total sobre el tiempo de animación. Si tienes múltiples animaciones definidas, puedes aplicarlas todas a la vez. @@ -251,32 +251,32 @@ El paquete de animaciones tiene una función `group()` para reproducir múltiple En este ejemplo, las animaciones `rotate` y `fade-in` se disparan al mismo tiempo. -### Animando los elementos de una lista que se reordena +### Animando los elementos de una lista que se reordena {#animating-the-items-of-a-reordering-list} El reordenamiento de elementos en una lista funciona de forma inmediata usando las técnicas descritas anteriormente. No se requiere ningún trabajo especial adicional. Los elementos en un bucle `@for` serán eliminados y re-agregados correctamente, lo que disparará animaciones usando `@starting-styles` para animaciones de entrada. Alternativamente, puedes usar `animate.enter` para este mismo comportamiento. Usa `animate.leave` para animar elementos a medida que se eliminan, como se ve en el ejemplo anterior. -#### Con el paquete de Aninaciones +#### Con el paquete de Aninaciones {#with-animations-package-7} - - - + + + -#### Con CSS nativo +#### Con CSS nativo {#with-native-css-8} - - - - + + + + -## Migrando usos de AnimationPlayer +## Migrando usos de AnimationPlayer {#migrating-usages-of-animationplayer} La clase `AnimationPlayer` permite acceso a una animación para hacer cosas más avanzadas como pausar, reproducir, reiniciar y finalizar una animación a través de código. Todas estas cosas también se pueden manejar nativamente. Puedes obtener animaciones de un elemento directamente usando [`Element.getAnimations()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations). Esto devuelve un array de cada [`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation) en ese elemento. Puedes usar la API de `Animation` para hacer mucho más de lo que podías con lo que ofrecía el `AnimationPlayer` del paquete de animaciones. Desde aquí puedes `cancel()`, `play()`, `pause()`, `reverse()` y mucho más. Esta API nativa debería proporcionar todo lo que necesitas para controlar tus animaciones. -## Transiciones de ruta +## Transiciones de ruta {#route-transitions} Puedes usar transiciones de vista para animar entre rutas. Consulta la [Guía de animaciones de transición de ruta](guide/routing/route-transition-animations) para comenzar. diff --git a/adev-es/src/content/guide/animations/overview.en.md b/adev-es/src/content/guide/animations/overview.en.md index e1d042d6..db2a70fa 100644 --- a/adev-es/src/content/guide/animations/overview.en.md +++ b/adev-es/src/content/guide/animations/overview.en.md @@ -1,10 +1,10 @@ # Introduction to Angular animations -IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations/enter-and-leave). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. +IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. -Animation provides the illusion of motion: HTML elements change styling over time. -Well-designed animations can make your application more fun and straightforward to use, but they aren't just cosmetic. -Animations can improve your application and user experience in a number of ways: +Animation provides the illusion of motion: HTML elements change styles over time. +Well-designed animations can make your application more intuitive and engaging, but they aren't just cosmetic. +Animations can improve your application and the user experience in a number of ways: - Without animations, web page transitions can seem abrupt and jarring - Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions @@ -32,13 +32,11 @@ To get started with adding Angular animations to your project, import the animat Import `provideAnimationsAsync` from `@angular/platform-browser/animations/async` and add it to the providers list in the `bootstrapApplication` function call. - +```ts {header: "Enabling Animations", linenums} bootstrapApplication(AppComponent, { - providers: [ - provideAnimationsAsync(), - ] + providers: [provideAnimationsAsync()], }); - +``` If you need to have an animation happen immediately when your application is loaded, @@ -54,7 +52,7 @@ For `NgModule` based applications import `BrowserAnimationsModule`, which introd If you plan to use specific animation functions in component files, import those functions from `@angular/animations`. - + See all [available animation functions](guide/legacy-animations#animations-api-summary) at the end of this guide. @@ -63,7 +61,7 @@ See all [available animation functions](guide/legacy-animations#animations-api-s In the component file, add a metadata property called `animations:` within the `@Component()` decorator. You put the trigger that defines an animation within the `animations` metadata property. - + @@ -72,7 +70,7 @@ You put the trigger that defines an animation within the `animations` metadata p Let's animate a transition that changes a single HTML element from one state to another. For example, you can specify that a button displays either **Open** or **Closed** based on the user's last action. When the button is in the `open` state, it's visible and yellow. -When it's the `closed` state, it's translucent and blue. +When it's in the `closed` state, it's translucent and blue. In HTML, these attributes are set using ordinary CSS styles such as color and opacity. In Angular, use the `style()` function to specify a set of CSS styles for use with animations. @@ -86,7 +84,7 @@ Run the following command in terminal to generate the component: ng g component open-close ``` -This will create the component at `src/app/open-close.component.ts`. +This will create the component at `src/app/open-close.ts`. ### Animation state and styles @@ -101,11 +99,11 @@ Let's see how Angular's [`state()`](api/animations/state) function works with th In this code snippet, multiple style attributes are set at the same time for the state. In the `open` state, the button has a height of 200 pixels, an opacity of 1, and a yellow background color. - + In the following `closed` state, the button has a height of 100 pixels, an opacity of 0.8, and a background color of blue. - + ### Transitions and timing @@ -126,19 +124,15 @@ The `animate()` function \(second argument of the transition function\) accepts The `timings` parameter takes either a number or a string defined in three parts. - - -animate (duration) - - +```ts +animate(duration); +``` or - - -animate ('duration delay easing') - - +```ts +animate('duration delay easing'); +``` The first part, `duration`, is required. The duration can be expressed in milliseconds as a number without quotes, or in seconds with quotes and a time specifier. @@ -177,7 +171,7 @@ HELPFUL: See the Material Design website's topic on [Natural easing curves](http This example provides a state transition from `open` to `closed` with a 1-second transition between states. - + In the preceding code snippet, the `=>` operator indicates unidirectional transitions, and `<=>` is bidirectional. Within the transition, `animate()` specifies how long the transition takes. @@ -185,7 +179,7 @@ In this case, the state change from `open` to `closed` takes 1 second, expressed This example adds a state transition from the `closed` state to the `open` state with a 0.5-second transition animation arc. - + HELPFUL: Some additional notes on using styles within [`state`](api/animations/state) and `transition` functions. @@ -194,11 +188,9 @@ HELPFUL: Some additional notes on using styles within [`state`](api/animations/s - When animations are disabled, `transition()` styles can be skipped, but [`state()`](api/animations/state) styles can't - Include multiple state pairs within the same `transition()` argument: - - - transition( 'on => off, off => void' ) - - + ```ts + transition('on => off, off => void'); + ``` ### Triggering the animation @@ -220,22 +212,20 @@ However, it's possible for multiple triggers to be active at once. Animations are defined in the metadata of the component that controls the HTML element to be animated. Put the code that defines your animations under the `animations:` property within the `@Component()` decorator. - + When you've defined an animation trigger for a component, attach it to an element in that component's template by wrapping the trigger name in brackets and preceding it with an `@` symbol. Then, you can bind the trigger to a template expression using standard Angular property binding syntax as shown below, where `triggerName` is the name of the trigger, and `expression` evaluates to a defined animation state. - - -
    ; - -
    +```angular-html +
    +``` The animation is executed or triggered when the expression value changes to a new state. The following code snippet binds the trigger to the value of the `isOpen` property. - + In this example, when the `isOpen` expression evaluates to a defined state of `open` or `closed`, it notifies the trigger `openClose` of a state change. Then it's up to the `openClose` code to handle the state change and kick off a state change animation. @@ -252,9 +242,9 @@ In the HTML template file, use the trigger name to attach the defined animations Here are the code files discussed in the transition example. - - - + + + ### Summary diff --git a/adev-es/src/content/guide/animations/overview.md b/adev-es/src/content/guide/animations/overview.md index f059d6a1..24b33f41 100644 --- a/adev-es/src/content/guide/animations/overview.md +++ b/adev-es/src/content/guide/animations/overview.md @@ -1,6 +1,6 @@ # Introducción a las animaciones en Angular -IMPORTANTE: El paquete `@angular/animations` ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con `animate.enter` y `animate.leave` para animaciones en todo código nuevo. Aprende más en la nueva [guía de animaciones](guide/animations/enter-and-leave) de entrada y salida. También consulta [Migrando del paquete de Animations de Angular](guide/animations/migration) para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones. +IMPORTANTE: El paquete `@angular/animations` ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con `animate.enter` y `animate.leave` para animaciones en todo código nuevo. Aprende más en la nueva [guía de animaciones](/guide/animations) de entrada y salida. También consulta [Migrando del paquete de Animations de Angular](guide/animations/migration) para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones. La animación proporciona la ilusión de movimiento: los elementos HTML cambian su estilo a lo largo del tiempo. Las animaciones bien diseñadas pueden hacer que tu aplicación sea más divertida y fácil de usar, pero no son solo cosméticas. @@ -18,11 +18,11 @@ El sistema de animación de Angular está construido sobre la funcionalidad CSS, Esto incluye posiciones, tamaños, transformaciones, colores, bordes y más. El W3C mantiene una lista de propiedades animables en su página [CSS Transitions](https://www.w3.org/TR/css-transitions-1). -## Acerca de esta guía +## Acerca de esta guía {#about-this-guide} Esta guía cubre las características básicas de animación de Angular para que comiences a agregar animaciones de Angular a tu proyecto. -## Primeros pasos +## Primeros pasos {#getting-started} Los módulos principales de Angular para animaciones son `@angular/animations` y `@angular/platform-browser`. @@ -54,20 +54,20 @@ Para aplicaciones basadas en `NgModule` importa `BrowserAnimationsModule`, que i Si planeas usar funciones de animación específicas en archivos de componentes, importa esas funciones desde `@angular/animations`. - + -Consulta todas las [funciones de animación disponibles](guide/legacy-animations#resumen-de-la-api-de-animaciones) al final de esta guía. +Consulta todas las [funciones de animación disponibles](guide/legacy-animations#animations-api-summary) al final de esta guía. En el archivo del componente, agrega una propiedad de metadatos llamada `animations:` dentro del decorador `@Component()`. Colocas el trigger que define una animación dentro de la propiedad de metadatos `animations`. - + -## Animando una transición +## Animando una transición {#animating-a-transition} Animemos una transición que cambia un elemento HTML único de un estado a otro. Por ejemplo, puedes especificar que un botón muestre **Abrir** o **Cerrado** basándose en la última acción del usuario. @@ -86,9 +86,9 @@ Ejecuta el siguiente comando en la terminal para generar el componente: ng g component open-close ``` -Esto creará el componente en `src/app/open-close.component.ts`. +Esto creará el componente en `src/app/open-close.ts`. -### Estado y estilos de animación +### Estado y estilos de animación {#animation-state-and-styles} Usa la función [`state()`](api/animations/state) de Angular para definir diferentes estados para llamar al final de cada transición. Esta función acepta dos argumentos: @@ -101,13 +101,13 @@ Veamos cómo funciona la función [`state()`](api/animations/state) de Angular c En este fragmento de código, se establecen múltiples atributos de estilo al mismo tiempo para el estado. En el estado `open`, el botón tiene una altura de 200 píxeles, una opacidad de 1 y un color de fondo amarillo. - + En el siguiente estado `closed`, el botón tiene una altura de 100 píxeles, una opacidad de 0.8 y un color de fondo azul. - + -### Transiciones y tiempo +### Transiciones y tiempo {#transitions-and-timing} En Angular, puedes establecer múltiples estilos sin ninguna animación. Sin embargo, sin un mayor refinamiento, el botón se transforma instantáneamente sin desvanecimiento, sin reducción u otro indicador visible de que está ocurriendo un cambio. @@ -120,7 +120,7 @@ Usa la función `animate()` para definir la duración, el retraso y el easing de Usa la función `animate()` para definir la función `keyframes()` para animaciones de múltiples pasos. Estas definiciones se colocan en el segundo argumento de la función `animate()`. -#### Metadatos de animación: duración, retraso y easing +#### Metadatos de animación: duración, retraso y easing {#animation-metadata-duration-delay-and-easing} La función `animate()` (segundo argumento de la función de transición) acepta los parámetros de entrada `timings` y `styles`. @@ -177,7 +177,7 @@ Por ejemplo, `ease-in` hace que la animación comience lentamente y gane velocid Este ejemplo proporciona una transición de estado de `open` a `closed` con una transición de 1 segundo entre estados. - + En el fragmento de código anterior, el operador `=>` indica transiciones unidireccionales, y `<=>` es bidireccional. Dentro de la transición, `animate()` especifica cuánto tiempo toma la transición. @@ -185,7 +185,7 @@ En este caso, el cambio de estado de `open` a `closed` toma 1 segundo, expresado Este ejemplo agrega una transición de estado del estado `closed` al estado `open` con un arco de animación de transición de 0.5 segundos. - + ÚTIL: Algunas notas adicionales sobre el uso de estilos dentro de las funciones [`state`](api/animations/state) y `transition`. @@ -200,7 +200,7 @@ Este ejemplo agrega una transición de estado del estado `closed` al estado `ope -### Disparando la animación +### Disparando la animación {#triggering-the-animation} Una animación requiere un _trigger_, para que sepa cuándo comenzar. La función `trigger()` recopila los estados y transiciones, y le da a la animación un nombre, para que puedas adjuntarla al elemento disparador en la plantilla HTML. @@ -215,12 +215,12 @@ El trigger describe los estados open y closed, y los tiempos para las dos transi ÚTIL: Dentro de cada llamada a la función `trigger()`, un elemento solo puede estar en un estado en cualquier momento dado. Sin embargo, es posible que múltiples triggers estén activos a la vez. -### Definiendo animaciones y adjuntándolas a la plantilla HTML +### Definiendo animaciones y adjuntándolas a la plantilla HTML {#defining-animations-and-attaching-them-to-the-html-template} Las animaciones se definen en los metadatos del componente que controla el elemento HTML a animar. Coloca el código que define tus animaciones bajo la propiedad `animations:` dentro del decorador `@Component()`. - + Cuando hayas definido un trigger de animación para un componente, adjúntalo a un elemento en la plantilla de ese componente envolviendo el nombre del trigger entre corchetes y precediéndolo con un símbolo `@`. Luego, puedes enlazar el trigger a una expresión de plantilla usando la sintaxis de enlace de propiedad estándar de Angular como se muestra a continuación, donde `triggerName` es el nombre del trigger, y `expression` se evalúa a un estado de animación definido. @@ -235,7 +235,7 @@ La animación se ejecuta o dispara cuando el valor de la expresión cambia a un El siguiente fragmento de código enlaza el trigger al valor de la propiedad `isOpen`. - + En este ejemplo, cuando la expresión `isOpen` se evalúa a un estado definido de `open` o `closed`, notifica al trigger `openClose` de un cambio de estado. Luego depende del código `openClose` manejar el cambio de estado y lanzar una animación de cambio de estado. @@ -247,23 +247,23 @@ Por ejemplo, usa `*ngIf` con el trigger de animación en la plantilla HTML. En el archivo de plantilla HTML, usa el nombre del trigger para adjuntar las animaciones definidas al elemento HTML a animar. -### Revisión del código +### Revisión del código {#code-review} Aquí están los archivos de código discutidos en el ejemplo de transición. - - - + + + -### Resumen +### Resumen {#summary} Aprendiste a agregar animación a una transición entre dos estados, usando `style()` y [`state()`](api/animations/state) junto con `animate()` para el tiempo. Aprende sobre características más avanzadas en las animaciones de Angular en la sección de Animaciones, comenzando con técnicas avanzadas en [transiciones y triggers](guide/legacy-animations/transition-and-triggers). -## Resumen de la API de animaciones +## Resumen de la API de animaciones {#animations-api-summary} La API funcional proporcionada por el módulo `@angular/animations` proporciona un lenguaje específico de dominio (DSL) para crear y controlar animaciones en aplicaciones Angular. Consulta la [referencia de la API](api#animations) para un listado completo y detalles de sintaxis de las funciones principales y estructuras de datos relacionadas. @@ -286,7 +286,7 @@ Consulta la [referencia de la API](api#animations) para un listado completo y de -## Más sobre animaciones de Angular +## Más sobre animaciones de Angular {#more-on-angular-animations} ÚTIL: Consulta esta [presentación](https://www.youtube.com/watch?v=rnTK9meY5us), mostrada en la conferencia AngularConnect en noviembre de 2017, y el [código fuente](https://github.com/matsko/animationsftw.in) adjunto. diff --git a/adev-es/src/content/guide/animations/reusable-animations.en.md b/adev-es/src/content/guide/animations/reusable-animations.en.md index 17c2e9bd..7aa3fcab 100644 --- a/adev-es/src/content/guide/animations/reusable-animations.en.md +++ b/adev-es/src/content/guide/animations/reusable-animations.en.md @@ -1,15 +1,15 @@ # Reusable animations -IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations/enter-and-leave). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. +IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. -This topic provides some examples of how to create reusable animations. +This topic provides examples of how to create reusable animations. ## Create reusable animations To create a reusable animation, use the [`animation()`](api/animations/animation) function to define an animation in a separate `.ts` file and declare this animation definition as a `const` export variable. -You can then import and reuse this animation in any of your application components using the [`useAnimation()`](api/animations/useAnimation) function. +You can then import and reuse this animation in your application components using the [`useAnimation()`](api/animations/useAnimation) function. - + In the preceding code snippet, `transitionAnimation` is made reusable by declaring it as an export variable. @@ -18,12 +18,12 @@ HELPFUL: The `height`, `opacity`, `backgroundColor`, and `time` inputs are repla You can also export a part of an animation. For example, the following snippet exports the animation `trigger`. - + -From this point, you can import reusable animation variables in your component class. +From this point, you can import reusable animation variables into your component class. For example, the following code snippet imports the `transitionAnimation` variable and uses it via the `useAnimation()` function. - + ## More on Angular animations diff --git a/adev-es/src/content/guide/animations/reusable-animations.md b/adev-es/src/content/guide/animations/reusable-animations.md index 1ef64a29..939e2560 100644 --- a/adev-es/src/content/guide/animations/reusable-animations.md +++ b/adev-es/src/content/guide/animations/reusable-animations.md @@ -1,15 +1,15 @@ # Animaciones reutilizables -IMPORTANTE: El paquete `@angular/animations` ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con `animate.enter` y `animate.leave` para animaciones en todo código nuevo. Aprende más en la nueva [guía de animaciones](guide/animations/enter-and-leave) de entrada y salida. También consulta [Migrando del paquete de Animations de Angular](guide/animations/migration) para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones. +IMPORTANTE: El paquete `@angular/animations` ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con `animate.enter` y `animate.leave` para animaciones en todo código nuevo. Aprende más en la nueva [guía de animaciones](/guide/animations) de entrada y salida. También consulta [Migrando del paquete de Animations de Angular](guide/animations/migration) para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones. Este tema proporciona algunos ejemplos de cómo crear animaciones reutilizables. -## Crear animaciones reutilizables +## Crear animaciones reutilizables {#create-reusable-animations} Para crear una animación reutilizable, usa la función [`animation()`](api/animations/animation) para definir una animación en un archivo `.ts` separado y declara esta definición de animación como una variable de exportación `const`. Luego puedes importar y reutilizar esta animación en cualquiera de los componentes de tu aplicación usando la función [`useAnimation()`](api/animations/useAnimation). - + En el fragmento de código anterior, `transitionAnimation` se hace reutilizable declarándola como una variable de exportación. @@ -18,14 +18,14 @@ En el fragmento de código anterior, `transitionAnimation` se hace reutilizable También puedes exportar una parte de una animación. Por ejemplo, el siguiente fragmento exporta el `trigger` de animación. - + Desde este punto, puedes importar variables de animación reutilizables en la clase de tu componente. Por ejemplo, el siguiente fragmento de código importa la variable `transitionAnimation` y la usa a través de la función `useAnimation()`. - + -## Más sobre animaciones de Angular +## Más sobre animaciones de Angular {#more-on-angular-animations} También puede que te interese lo siguiente: diff --git a/adev-es/src/content/guide/animations/route-animations.en.md b/adev-es/src/content/guide/animations/route-animations.en.md deleted file mode 100644 index 1fbbd973..00000000 --- a/adev-es/src/content/guide/animations/route-animations.en.md +++ /dev/null @@ -1,157 +0,0 @@ -# Route transition animations - -When a user navigates from one route to another, the Angular Router maps the URL path to the relevant component and displays its view. Animating this route transition can greatly enhance the user experience. The Router has support for the View Transitions API when navigating between routes in Chrome/Chromium browsers. - -HELPFUL: The Router's native View Transitions integration is currently in [developer preview](/reference/releases#developer-preview). Native View Transitions are also a relatively new feature so there may be limited support in some browsers. - -## How View Transitions work - -The native browser method that’s used for view transitions is `document.startViewTransition`. When `startViewTransition()` is called, the browser captures the current state of the page which includes taking a screenshot. The method takes a callback that updates the DOM and this function can be asynchronous. The new state is captured and the transition begins in the next animation frame when the promise returned by the callback resolves. - -Here’s an example of the startViewTransition api: - -```ts -document.startViewTransition(async () => { - await updateTheDOMSomehow(); -}); -``` - -If you’re curious to read more about the details of the browser API, the [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions) is an invaluable resource. - -## How the Router uses view transitions - -Several things happen after navigation starts in the router: route matching, loading lazy routes and components, executing guards and resolvers to name a few. Once these have completed successfully, the new routes are ready to be activated. This route activation is the DOM update that we want to perform as part of the view transition. - -When the view transition feature is enabled, navigation “pauses” and a call is made to the browser’s `startViewTransition` method. Once the `startViewTransition` callback executes (this happens asynchronously, as outlined in the spec here), navigation “resumes”. The remaining steps for the router navigation include updating the browser URL and activating or deactivating the matched routes (the DOM update). - -Finally, the callback passed to `startViewTransition` returns a Promise that resolves once Angular has finished rendering. As described above, this indicates to the browser that the new DOM state should be captured and the transition should begin. - -View transitions are a [progressive enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). If the browser does not support the API, the Router will perform the DOM updates without calling `startViewTransition` and the navigation will not be animated. - -## Enabling View Transitions in the Router - -To enable this feature, simply add `withViewTransitions` to the `provideRouter` or set `enableViewTransitions: true` in `RouterModule.forRoot`: - -```ts -// Standalone bootstrap -bootstrapApplication(MyApp, {providers: [ - provideRouter(ROUTES, withViewTransitions()), -]}); - -// NgModule bootstrap -@NgModule({ - imports: [RouterModule.forRoot(routes, {enableViewTransitions: true})] -}) -export class AppRouting {} -``` - -[Try the “count” example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-2dnvtm?file=src%2Fmain.ts) - -This example uses the counter application from the Chrome explainer and replaces the direct call to startViewTransition when the counter increments with a router navigation. - -## Using CSS to customize transitions - -View transitions can be customized with CSS. We can also instruct the browser to create separate elements for the transition by setting a view-transition-name. We can expand the first example by adding view-transition-name: count to the .count style in the Counter component. Then, in the global styles, we can define a custom animation for this view transition: - -```css -/* Custom transition */ -@keyframes rotate-out { - to { - transform: rotate(90deg); - } -} -@keyframes rotate-in { - from { - transform: rotate(-90deg); - } -} -::view-transition-old(count), -::view-transition-new(count) { - animation-duration: 200ms; - animation-name: -ua-view-transition-fade-in, rotate-in; -} -::view-transition-old(count) { - animation-name: -ua-view-transition-fade-out, rotate-out; -} -``` - -It is important that the view transition animations are defined in a global style file. They cannot be defined in the component styles because the default view encapsulation will scope the styles to the component. - -[Try the updated “count” example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-fwn4i7?file=src%2Fmain.ts) - -## Controlling transitions with onViewTransitionCreated - -The `withViewTransitions` router feature can also be called with an options object that includes an `onViewTransitionCreated` callback. This callback is run in an [injection context](/guide/di/dependency-injection-context#run-within-an-injection-context) and receives a [ViewTransitionInfo](/api/router/ViewTransitionInfo) object that includes the `ViewTransition` returned from `startViewTransition`, as well as the `ActivatedRouteSnapshot` that the navigation is transitioning from and the new one that it is transitioning to. - -This callback can be used for any number of customizations. For example, you might want to skip transitions under certain conditions. We use this on the new angular.dev docs site: - -```ts -withViewTransitions({ - onViewTransitionCreated: ({transition}) => { - const router = inject(Router); - const targetUrl = router.getCurrentNavigation()!.finalUrl!; - // Skip the transition if the only thing - // changing is the fragment and queryParams - const config = { - paths: 'exact', - matrixParams: 'exact', - fragment: 'ignored', - queryParams: 'ignored', - }; - - if (router.isActive(targetUrl, config)) { - transition.skipTransition(); - } - }, -}), -``` - -In this code snippet, we create a `UrlTree` from the `ActivatedRouteSnapshot` the navigation is going to. We then check with the Router to see if this `UrlTree` is already active, ignoring any differences in the fragment or query parameters. If it is already active, we call skipTransition which will skip the animation portion of the view transition. This is the case when clicking on an anchor link that will only scroll to another location in the same document. - -## Examples from the Chrome explainer adapted to Angular - -We’ve recreated some of the great examples from the Chrome Team in Angular for you to explore. - -### Transitioning elements don’t need to be the same DOM element - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transitioning_elements_dont_need_to_be_the_same_dom_element) -* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-dh8npr?file=src%2Fmain.ts) - -### Custom entry and exit animations - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#custom_entry_and_exit_transitions) -* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-8kly3o) - -### Async DOM updates and waiting for content - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#async_dom_updates_and_waiting_for_content) - -> During this time, the page is frozen, so delays here should be kept to a minimum…in some cases it’s better to avoid the delay altogether, and use the content you already have. - -The view transition feature in the Angular router does not provide a way to delay the animation. For the moment, our stance is that it’s always better to use the content you have rather than making the page non-interactive for any additional amount of time. - -### Handle multiple view transition styles with view transition types - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-types) -* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-vxzcam) - -### Handle multiple view transition styles with a class name on the view transition root (deprecated) - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#changing-on-navigation-type) -* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-nmnzzg?file=src%2Fmain.ts) - -### Transitioning without freezing other animations - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transitioning-without-freezing) -* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-76kgww) - -### Animating with Javascript - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#animating-with-javascript) -* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-cklnkm) - -## Native View Transitions Alternative - -Animating the transition between routes can also be done with the `@angular/animations` package. -The animation [triggers and transitions](/guide/animations/transition-and-triggers) -can be derived from the router state, such as the current URL or `ActivatedRoute`. diff --git a/adev-es/src/content/guide/animations/route-animations.md b/adev-es/src/content/guide/animations/route-animations.md deleted file mode 100644 index 6b780f3c..00000000 --- a/adev-es/src/content/guide/animations/route-animations.md +++ /dev/null @@ -1,157 +0,0 @@ -# Animaciones de transición de ruta - -Cuando un usuario navega de una ruta a otra, el Router de Angular mapea la ruta URL al componente relevante y muestra su vista. Animar esta transición de ruta puede mejorar enormemente la experiencia del usuario. El Router tiene soporte para la API de View Transitions al navegar entre rutas en navegadores Chrome/Chromium. - -ÚTIL: La integración nativa de View Transitions del Router está actualmente en [vista previa para desarrolladores](/reference/releases#developer-preview). Las View Transitions nativas también son una característica relativamente nueva, por lo que puede haber soporte limitado en algunos navegadores. - -## Cómo funcionan las View Transitions - -El método nativo del navegador que se usa para transiciones de vista es `document.startViewTransition`. Cuando se llama a `startViewTransition()`, el navegador captura el estado actual de la página, que incluye tomar una captura de pantalla. El método toma un callback que actualiza el DOM y esta función puede ser asíncrona. El nuevo estado se captura y la transición comienza en el siguiente frame de animación cuando la promesa devuelta por el callback se resuelve. - -Aquí hay un ejemplo de la API startViewTransition: - -```ts -document.startViewTransition(async () => { - await updateTheDOMSomehow(); -}); -``` - -Si tienes curiosidad por leer más sobre los detalles de la API del navegador, el [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions) es un recurso invaluable. - -## Cómo el Router usa transiciones de vista - -Varias cosas suceden después de que comienza la navegación en el router: coincidencia de rutas, carga de rutas y componentes lazy, ejecución de guards y resolvers, por nombrar algunos. Una vez que estos se han completado exitosamente, las nuevas rutas están listas para ser activadas. Esta activación de ruta es la actualización del DOM que queremos realizar como parte de la transición de vista. - -Cuando la característica de transición de vista está habilitada, la navegación se "pausa" y se hace una llamada al método `startViewTransition` del navegador. Una vez que se ejecuta el callback `startViewTransition` (esto sucede de forma asíncrona, como se describe en la especificación aquí), la navegación se "reanuda". Los pasos restantes para la navegación del router incluyen actualizar la URL del navegador y activar o desactivar las rutas coincidentes (la actualización del DOM). - -Finalmente, el callback pasado a `startViewTransition` devuelve una Promise que se resuelve una vez que Angular ha terminado de renderizar. Como se describió anteriormente, esto indica al navegador que el nuevo estado del DOM debe ser capturado y la transición debe comenzar. - -Las transiciones de vista son una [mejora progresiva](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). Si el navegador no soporta la API, el Router realizará las actualizaciones del DOM sin llamar a `startViewTransition` y la navegación no será animada. - -## Habilitando View Transitions en el Router - -Para habilitar esta característica, simplemente agrega `withViewTransitions` al `provideRouter` o establece `enableViewTransitions: true` en `RouterModule.forRoot`: - -```ts -// Standalone bootstrap -bootstrapApplication(MyApp, {providers: [ - provideRouter(ROUTES, withViewTransitions()), -]}); - -// NgModule bootstrap -@NgModule({ - imports: [RouterModule.forRoot(routes, {enableViewTransitions: true})] -}) -export class AppRouting {} -``` - -[Prueba el ejemplo "count" en StackBlitz](https://stackblitz.com/edit/stackblitz-starters-2dnvtm?file=src%2Fmain.ts) - -Este ejemplo usa la aplicación de contador del Chrome explainer y reemplaza la llamada directa a startViewTransition cuando el contador se incrementa con una navegación del router. - -## Usando CSS para personalizar transiciones - -Las transiciones de vista se pueden personalizar con CSS. También podemos instruir al navegador para crear elementos separados para la transición estableciendo un view-transition-name. Podemos expandir el primer ejemplo agregando view-transition-name: count al estilo .count en el componente Counter. Luego, en los estilos globales, podemos definir una animación personalizada para esta transición de vista: - -```css -/* Custom transition */ -@keyframes rotate-out { - to { - transform: rotate(90deg); - } -} -@keyframes rotate-in { - from { - transform: rotate(-90deg); - } -} -::view-transition-old(count), -::view-transition-new(count) { - animation-duration: 200ms; - animation-name: -ua-view-transition-fade-in, rotate-in; -} -::view-transition-old(count) { - animation-name: -ua-view-transition-fade-out, rotate-out; -} -``` - -Es importante que las animaciones de transición de vista se definan en un archivo de estilo global. No pueden definirse en los estilos del componente porque la encapsulación de vista predeterminada delimitará los estilos al componente. - -[Prueba el ejemplo "count" actualizado en StackBlitz](https://stackblitz.com/edit/stackblitz-starters-fwn4i7?file=src%2Fmain.ts) - -## Controlando transiciones con onViewTransitionCreated - -La característica del router `withViewTransitions` también se puede llamar con un objeto de opciones que incluye un callback `onViewTransitionCreated`. Este callback se ejecuta en un [contexto de inyección](/guide/di/dependency-injection-context#run-within-an-injection-context) y recibe un objeto [ViewTransitionInfo](/api/router/ViewTransitionInfo) que incluye la `ViewTransition` devuelta por `startViewTransition`, así como el `ActivatedRouteSnapshot` desde el que la navegación está haciendo transición y el nuevo al que está haciendo transición. - -Este callback se puede usar para cualquier número de personalizaciones. Por ejemplo, es posible que desees omitir transiciones bajo ciertas condiciones. Usamos esto en el nuevo sitio de documentación angular.dev: - -```ts -withViewTransitions({ - onViewTransitionCreated: ({transition}) => { - const router = inject(Router); - const targetUrl = router.getCurrentNavigation()!.finalUrl!; - // Skip the transition if the only thing - // changing is the fragment and queryParams - const config = { - paths: 'exact', - matrixParams: 'exact', - fragment: 'ignored', - queryParams: 'ignored', - }; - - if (router.isActive(targetUrl, config)) { - transition.skipTransition(); - } - }, -}), -``` - -En este fragmento de código, creamos un `UrlTree` desde el `ActivatedRouteSnapshot` al que va la navegación. Luego verificamos con el Router para ver si este `UrlTree` ya está activo, ignorando cualquier diferencia en el fragmento o parámetros de consulta. Si ya está activo, llamamos a skipTransition que omitirá la porción de animación de la transición de vista. Este es el caso al hacer clic en un enlace de anclaje que solo se desplazará a otra ubicación en el mismo documento. - -## Ejemplos del Chrome explainer adaptados a Angular - -Hemos recreado algunos de los excelentes ejemplos del Chrome Team en Angular para que explores. - -### Los elementos de transición no necesitan ser el mismo elemento DOM - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transitioning_elements_dont_need_to_be_the_same_dom_element) -* [Ejemplo de Angular en StackBlitz](https://stackblitz.com/edit/stackblitz-starters-dh8npr?file=src%2Fmain.ts) - -### Animaciones personalizadas de entrada y salida - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#custom_entry_and_exit_transitions) -* [Ejemplo de Angular en StackBlitz](https://stackblitz.com/edit/stackblitz-starters-8kly3o) - -### Actualizaciones asíncronas del DOM y espera de contenido - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#async_dom_updates_and_waiting_for_content) - -> Durante este tiempo, la página está congelada, por lo que los retrasos aquí deben mantenerse al mínimo... en algunos casos es mejor evitar el retraso por completo y usar el contenido que ya tienes. - -La característica de transición de vista en el Router de Angular no proporciona una forma de retrasar la animación. Por el momento, nuestra postura es que siempre es mejor usar el contenido que tienes en lugar de hacer que la página no sea interactiva por cualquier cantidad de tiempo adicional. - -### Manejar múltiples estilos de transición de vista con tipos de transición de vista - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-types) -* [Ejemplo de Angular en StackBlitz](https://stackblitz.com/edit/stackblitz-starters-vxzcam) - -### Manejar múltiples estilos de transición de vista con un nombre de clase en la raíz de transición de vista (deprecado) - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#changing-on-navigation-type) -* [Ejemplo de Angular en StackBlitz](https://stackblitz.com/edit/stackblitz-starters-nmnzzg?file=src%2Fmain.ts) - -### Transición sin congelar otras animaciones - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transitioning-without-freezing) -* [Ejemplo de Angular en StackBlitz](https://stackblitz.com/edit/stackblitz-starters-76kgww) - -### Animando con Javascript - -* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#animating-with-javascript) -* [Ejemplo de Angular en StackBlitz](https://stackblitz.com/edit/stackblitz-starters-cklnkm) - -## Alternativa de View Transitions nativas - -Animar la transición entre rutas también se puede hacer con el paquete `@angular/animations`. -Los [triggers y transiciones](/guide/animations/transition-and-triggers) de animación -se pueden derivar del estado del router, como la URL actual o `ActivatedRoute`. diff --git a/adev-es/src/content/guide/animations/transition-and-triggers.en.md b/adev-es/src/content/guide/animations/transition-and-triggers.en.md index 13be6110..7cfe7596 100644 --- a/adev-es/src/content/guide/animations/transition-and-triggers.en.md +++ b/adev-es/src/content/guide/animations/transition-and-triggers.en.md @@ -1,8 +1,8 @@ # Animation transitions and triggers -IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations/enter-and-leave). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. +IMPORTANT: The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](guide/animations). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps. -This guide goes into depth on special transition states such as the `*` wildcard and `void`. It shows how these special states are used for elements entering and leaving a view. +This guide goes into depth on special transition states such as the `*` wildcard and `void`. It also shows how these states are used for elements entering and leaving a view. This section also explores multiple animation triggers, animation callbacks, and sequence-based animation using keyframes. ## Predefined states and wildcard matching @@ -23,11 +23,11 @@ Instead of defining each state-to-state transition pair, any transition to `clos This allows the addition of new states without having to include separate transitions for each one. - + Use a double arrow syntax to specify state-to-state transitions in both directions. - + ### Use wildcard state with multiple transition states @@ -37,7 +37,7 @@ If the button can change from `open` to either `closed` or something like `inPro wildcard state with 3 states - + The `* => *` transition applies when any change between two states takes place. @@ -52,7 +52,7 @@ To do this, list the more specific transitions _before_ `* => *`. Use the wildcard `*` with a style to tell the animation to use whatever the current style value is, and animate with that. Wildcard is a fallback value that's used if the state being animated isn't declared within the trigger. - + ### Void state @@ -76,7 +76,7 @@ Add a new behavior: - When you add a hero to the list of heroes, it appears to fly onto the page from the left - When you remove a hero from the list, it appears to fly out to the right - + In the preceding code, you applied the `void` state when the HTML element isn't attached to a view. @@ -85,12 +85,12 @@ In the preceding code, you applied the `void` state when the HTML element isn't `:enter` and `:leave` are aliases for the `void => *` and `* => void` transitions. These aliases are used by several animation functions. - +```ts {hideCopy} transition ( ':enter', [ … ] ); // alias for void => _ transition ( ':leave', [ … ] ); // alias for _ => void - +``` It's harder to target an element that is entering a view because it isn't in the DOM yet. Use the aliases `:enter` and `:leave` to target HTML elements that are inserted or removed from a view. @@ -99,17 +99,17 @@ Use the aliases `:enter` and `:leave` to target HTML elements that are inserted The `:enter` transition runs when any `*ngIf` or `*ngFor` views are placed on the page, and `:leave` runs when those views are removed from the page. -IMPORTANT: Entering/leaving behaviors can sometime be confusing. +IMPORTANT: Entering/leaving behaviors can sometimes be confusing. As a rule of thumb consider that any element being added to the DOM by Angular passes via the `:enter` transition. Only elements being directly removed from the DOM by Angular pass via the `:leave` transition. For example, an element's view is removed from the DOM because its parent is being removed from the DOM. This example has a special trigger for the enter and leave animation called `myInsertRemoveTrigger`. The HTML template contains the following code. - + In the component file, the `:enter` transition sets an initial opacity of 0. It then animates it to change that opacity to 1 as the element is inserted into the view. - + Note that this example doesn't need to use [`state()`](api/animations/state). @@ -121,13 +121,13 @@ Use these to kick off a transition when a numeric value has increased or decreas HELPFUL: The following example uses `query()` and `stagger()` methods. For more information on these methods, see the [complex sequences](guide/legacy-animations/complex-sequences) page. - + ## Boolean values in transitions If a trigger contains a Boolean value as a binding value, then this value can be matched using a `transition()` expression that compares `true` and `false`, or `1` and `0`. - + In the code snippet above, the HTML template binds a `
    ` element to a trigger named `openClose` with a status expression of `isOpen`, and with possible values of `true` and `false`. This pattern is an alternative to the practice of creating two named states like `open` and `close`. @@ -136,7 +136,7 @@ Inside the `@Component` metadata under the `animations:` property, when the stat In this case, the animation uses whatever height the element already had before the animation started. When the element is `closed`, the element gets animated to a height of 0, which makes it invisible. - + ## Multiple animation triggers @@ -156,8 +156,8 @@ When true, the `@.disabled` binding prevents all animations from rendering. The following code sample shows how to use this feature. - - + + When the `@.disabled` binding is true, the `@childAnimation` trigger doesn't kick off. @@ -177,7 +177,7 @@ Those elements can still animate. To turn off all animations for an Angular application, place the `@.disabled` host binding on the topmost Angular component. - + HELPFUL: Disabling animations application-wide is useful during end-to-end \(E2E\) testing. @@ -186,12 +186,12 @@ HELPFUL: Disabling animations application-wide is useful during end-to-end \(E2E The animation `trigger()` function emits _callbacks_ when it starts and when it finishes. The following example features a component that contains an `openClose` trigger. - + In the HTML template, the animation event is passed back via `$event`, as `@triggerName.start` and `@triggerName.done`, where `triggerName` is the name of the trigger being used. In this example, the trigger `openClose` appears as follows. - + A potential use for animation callbacks could be to cover for a slow API call, such as a database lookup. For example, an **InProgress** button can be set up to have its own looping animation while the backend system operation finishes. @@ -204,7 +204,7 @@ An animation can influence an end user to _perceive_ the operation as faster, ev Callbacks can serve as a debugging tool, for example in conjunction with `console.warn()` to view the application's progress in a browser's Developer JavaScript Console. The following code snippet creates console log output for the original example, a button with the two states of `open` and `closed`. - + ## Keyframes @@ -217,7 +217,7 @@ For example, the button, instead of fading, could change color several times ove The code for this color change might look like this. - + ### Offset @@ -233,13 +233,13 @@ Specifying an offset of 0.8 for the middle transition in the preceding example m The code with offsets specified would be as follows. - + You can combine keyframes with `duration`, `delay`, and `easing` within a single animation. ### Keyframes with a pulsation -Use keyframes to create a pulse effect in your animations by defining styles at specific offset throughout the animation. +Use keyframes to create a pulse effect in your animations by defining styles at specific offsets throughout the animation. Here's an example of using keyframes to create a pulse effect: @@ -250,11 +250,11 @@ Here's an example of using keyframes to create a pulse effect: The code snippet for this animation might look like this. - + ### Animatable properties and units -Angular animations support builds on top of web animations, so you can animate any property that the browser considers animatable. +Angular animations are built on top of web animations, so you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its [CSS Transitions](https://www.w3.org/TR/css-transitions-1) page. @@ -285,7 +285,7 @@ In these cases, you can use a special wildcard `*` property value under `style() The following example has a trigger called `shrinkOut`, used when an HTML element leaves the page. The animation takes whatever height the element has before it leaves, and animates from that height to zero. - + ### Keyframes summary diff --git a/adev-es/src/content/guide/animations/transition-and-triggers.md b/adev-es/src/content/guide/animations/transition-and-triggers.md index e807c698..27e936b0 100644 --- a/adev-es/src/content/guide/animations/transition-and-triggers.md +++ b/adev-es/src/content/guide/animations/transition-and-triggers.md @@ -1,15 +1,15 @@ # Transiciones y triggers de animación -IMPORTANTE: El paquete `@angular/animations` ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con `animate.enter` y `animate.leave` para animaciones en todo código nuevo. Aprende más en la nueva [guía de animaciones](guide/animations/enter-and-leave) de entrada y salida. También consulta [Migrando del paquete de Animations de Angular](guide/animations/migration) para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones. +IMPORTANTE: El paquete `@angular/animations` ahora está deprecado. El equipo de Angular recomienda usar CSS nativo con `animate.enter` y `animate.leave` para animaciones en todo código nuevo. Aprende más en la nueva [guía de animaciones](/guide/animations) de entrada y salida. También consulta [Migrando del paquete de Animations de Angular](guide/animations/migration) para aprender cómo puedes comenzar a migrar a animaciones CSS puras en tus aplicaciones. Esta guía profundiza en estados de transición especiales como el comodín `*` y `void`. Muestra cómo estos estados especiales se usan para elementos que entran y salen de una vista. Esta sección también explora múltiples triggers de animación, callbacks de animación y animación basada en secuencias usando keyframes. -## Estados predefinidos y coincidencia de comodines +## Estados predefinidos y coincidencia de comodines {#predefined-states-and-wildcard-matching} En Angular, los estados de transición se pueden definir explícitamente a través de la función [`state()`](api/animations/state), o usando los estados predefinidos `*` comodín y `void`. -### Estado comodín +### Estado comodín {#wildcard-state} Un asterisco `*` o _comodín_ coincide con cualquier estado de animación. Esto es útil para definir transiciones que se aplican independientemente del estado inicial o final del elemento HTML. @@ -23,13 +23,13 @@ En lugar de definir cada par de transición estado-a-estado, cualquier transici Esto permite la adición de nuevos estados sin tener que incluir transiciones separadas para cada uno. - + Usa una sintaxis de doble flecha para especificar transiciones estado-a-estado en ambas direcciones. - + -### Usa el estado comodín con múltiples estados de transición +### Usa el estado comodín con múltiples estados de transición {#use-wildcard-state-with-multiple-transition-states} En el ejemplo del botón de dos estados, el comodín no es tan útil porque solo hay dos estados posibles, `open` y `closed`. En general, usa estados comodín cuando un elemento tiene múltiples estados potenciales a los que puede cambiar. @@ -37,7 +37,7 @@ Si el botón puede cambiar de `open` a `closed` o algo como `inProgress`, usar u wildcard state with 3 states - + La transición `* => *` se aplica cuando tiene lugar cualquier cambio entre dos estados. @@ -47,19 +47,19 @@ Por ejemplo, define cambios de estilo o animaciones que se aplicarían solo a `o Para hacer esto, lista las transiciones más específicas _antes de_ `* => *`. -### Usa comodines con estilos +### Usa comodines con estilos {#use-wildcards-with-styles} Usa el comodín `*` con un estilo para decirle a la animación que use cualquier valor de estilo actual y anime con eso. El comodín es un valor de respaldo que se usa si el estado que se está animando no está declarado dentro del trigger. - + -### Estado void +### Estado void {#void-state} Usa el estado `void` para configurar transiciones para un elemento que está entrando o saliendo de una página. -Consulta [Animando entrada y salida de una vista](guide/legacy-animations/transition-and-triggers#alias-enter-y-leave). +Consulta [Animando entrada y salida de una vista](guide/legacy-animations/transition-and-triggers#aliases-enter-and-leave). -### Combinar estados comodín y void +### Combinar estados comodín y void {#combine-wildcard-and-void-states} Combina estados comodín y void en una transición para disparar animaciones que entran y salen de la página: @@ -67,7 +67,7 @@ Combina estados comodín y void en una transición para disparar animaciones que - Una transición de `void => *` se aplica cuando el elemento entra en una vista, independientemente del estado que asuma al entrar - El estado comodín `*` coincide con _cualquier_ estado, incluyendo `void` -## Animar entrada y salida de una vista +## Animar entrada y salida de una vista {#animate-entering-and-leaving-a-view} Esta sección muestra cómo animar elementos entrando o saliendo de una página. @@ -76,11 +76,11 @@ Agrega un nuevo comportamiento: - Cuando agregas un héroe a la lista de héroes, parece volar a la página desde la izquierda - Cuando eliminas un héroe de la lista, parece volar hacia la derecha - + En el código anterior, aplicaste el estado `void` cuando el elemento HTML no está adjunto a una vista. -## Alias :enter y :leave +## Alias :enter y :leave {#aliases-enter-and-leave} `:enter` y `:leave` son alias para las transiciones `void => *` y `* => void`. Estos alias son usados por varias funciones de animación. @@ -95,7 +95,7 @@ transition ( ':leave', [ … ] ); // alias for * => void Es más difícil dirigirse a un elemento que está entrando en una vista porque aún no está en el DOM. Usa los alias `:enter` y `:leave` para dirigirte a elementos HTML que se insertan o eliminan de una vista. -### Usa `*ngIf` y `*ngFor` con :enter y :leave +### Usa `*ngIf` y `*ngFor` con :enter y :leave {#use-ngif-and-ngfor-with-enter-and-leave} La transición `:enter` se ejecuta cuando cualquier vista `*ngIf` o `*ngFor` se coloca en la página, y `:leave` se ejecuta cuando esas vistas se eliminan de la página. @@ -105,15 +105,15 @@ Como regla general, considera que cualquier elemento que Angular agregue al DOM Este ejemplo tiene un trigger especial para la animación de entrada y salida llamado `myInsertRemoveTrigger`. La plantilla HTML contiene el siguiente código. - + En el archivo del componente, la transición `:enter` establece una opacidad inicial de 0. Luego la anima para cambiar esa opacidad a 1 a medida que el elemento se inserta en la vista. - + Nota que este ejemplo no necesita usar [`state()`](api/animations/state). -## Transición :increment y :decrement +## Transición :increment y :decrement {#transition-increment-and-decrement} La función `transition()` acepta otros valores de selector, `:increment` y `:decrement`. Úsalos para iniciar una transición cuando un valor numérico ha aumentado o disminuido en valor. @@ -121,13 +121,13 @@ La función `transition()` acepta otros valores de selector, `:increment` y `:de ÚTIL: El siguiente ejemplo usa los métodos `query()` y `stagger()`. Para más información sobre estos métodos, consulta la página de [secuencias complejas](guide/legacy-animations/complex-sequences). - + -## Valores booleanos en transiciones +## Valores booleanos en transiciones {#boolean-values-in-transitions} Si un trigger contiene un valor booleano como valor de enlace, entonces este valor se puede emparejar usando una expresión `transition()` que compara `true` y `false`, o `1` y `0`. - + En el fragmento de código anterior, la plantilla HTML enlaza un elemento `
    ` a un trigger llamado `openClose` con una expresión de estado de `isOpen`, y con valores posibles de `true` y `false`. Este patrón es una alternativa a la práctica de crear dos estados nombrados como `open` y `close`. @@ -136,19 +136,19 @@ Dentro de los metadatos del `@Component` bajo la propiedad `animations:`, cuando En este caso, la animación usa cualquier altura que el elemento ya tenía antes de que comenzara la animación. Cuando el elemento está `closed`, el elemento se anima a una altura de 0, lo que lo hace invisible. - + -## Múltiples triggers de animación +## Múltiples triggers de animación {#multiple-animation-triggers} Puedes definir más de un trigger de animación para un componente. Adjunta triggers de animación a diferentes elementos, y las relaciones padre-hijo entre los elementos afectan cómo y cuándo se ejecutan las animaciones. -### Animaciones padre-hijo +### Animaciones padre-hijo {#parent-child-animations} Cada vez que se dispara una animación en Angular, la animación padre siempre tiene prioridad y las animaciones hijas se bloquean. Para que se ejecute una animación hija, la animación padre debe consultar cada uno de los elementos que contienen animaciones hijas. Luego permite que las animaciones se ejecuten usando la función [`animateChild()`](api/animations/animateChild). -#### Deshabilitar una animación en un elemento HTML +#### Deshabilitar una animación en un elemento HTML {#disable-an-animation-on-an-html-element} Un enlace de control de animación especial llamado `@.disabled` se puede colocar en un elemento HTML para apagar animaciones en ese elemento, así como en cualquier elemento anidado. Cuando es true, el enlace `@.disabled` previene que se rendericen todas las animaciones. @@ -156,8 +156,8 @@ Cuando es true, el enlace `@.disabled` previene que se rendericen todas las anim El siguiente ejemplo de código muestra cómo usar esta característica. - - + + Cuando el enlace `@.disabled` es true, el trigger `@childAnimation` no se activa. @@ -173,25 +173,25 @@ Esos elementos aún pueden animar. * Una animación hija puede ser consultada por un padre y luego animada posteriormente con la función `animateChild()` -#### Deshabilitar todas las animaciones +#### Deshabilitar todas las animaciones {#disable-all-animations} Para desactivar todas las animaciones para una aplicación Angular, coloca el enlace host `@.disabled` en el componente Angular superior. - + ÚTIL: Deshabilitar animaciones en toda la aplicación es útil durante las pruebas end-to-end (E2E). -## Callbacks de animación +## Callbacks de animación {#animation-callbacks} La función `trigger()` de animación emite _callbacks_ cuando comienza y cuando termina. El siguiente ejemplo presenta un componente que contiene un trigger `openClose`. - + En la plantilla HTML, el evento de animación se devuelve a través de `$event`, como `@triggerName.start` y `@triggerName.done`, donde `triggerName` es el nombre del trigger que se está usando. En este ejemplo, el trigger `openClose` aparece de la siguiente manera. - + Un uso potencial para callbacks de animación podría ser cubrir una llamada API lenta, como una búsqueda en base de datos. Por ejemplo, se puede configurar un botón **InProgress** para tener su propia animación en bucle mientras la operación del sistema backend finaliza. @@ -204,7 +204,7 @@ Una animación puede influir en que un usuario final _perciba_ la operación com Los callbacks pueden servir como herramienta de depuración, por ejemplo en conjunto con `console.warn()` para ver el progreso de la aplicación en la Consola de JavaScript del desarrollador del navegador. El siguiente fragmento de código crea salida de registro de consola para el ejemplo original, un botón con los dos estados de `open` y `closed`. - + ## Keyframes @@ -217,7 +217,7 @@ Por ejemplo, el botón, en lugar de desvanecerse, podría cambiar de color varia El código para este cambio de color podría verse así. - + ### Offset @@ -233,11 +233,11 @@ Especificar un offset de 0.8 para la transición media en el ejemplo anterior po El código con offsets especificados sería el siguiente. - + Puedes combinar keyframes con `duration`, `delay` y `easing` dentro de una sola animación. -### Keyframes con pulsación +### Keyframes con pulsación {#keyframes-with-a-pulsation} Usa keyframes para crear un efecto de pulso en tus animaciones definiendo estilos en offsets específicos a lo largo de la animación. @@ -250,9 +250,9 @@ Aquí hay un ejemplo de uso de keyframes para crear un efecto de pulso: El fragmento de código para esta animación podría verse así. - + -### Propiedades animables y unidades +### Propiedades animables y unidades {#animatable-properties-and-units} Las animaciones de Angular se construyen sobre animaciones web, por lo que puedes animar cualquier propiedad que el navegador considere animable. Esto incluye posiciones, tamaños, transformaciones, colores, bordes y más. @@ -274,7 +274,7 @@ Expresar 50 píxeles como `50` es lo mismo que decir `'50px'`. ÚTIL: La cadena `"50"` no sería considerada válida. -### Cálculo automático de propiedades con comodines +### Cálculo automático de propiedades con comodines {#automatic-property-calculation-with-wildcards} A veces, el valor de una propiedad de estilo dimensional no se conoce hasta el tiempo de ejecución. Por ejemplo, los elementos a menudo tienen anchos y alturas que dependen de su contenido o del tamaño de la pantalla. @@ -285,13 +285,13 @@ En estos casos, puedes usar un valor de propiedad comodín `*` especial bajo `st El siguiente ejemplo tiene un trigger llamado `shrinkOut`, usado cuando un elemento HTML sale de la página. La animación toma cualquier altura que el elemento tenga antes de salir, y la anima desde esa altura hasta cero. - + -### Resumen de keyframes +### Resumen de keyframes {#keyframes-summary} La función `keyframes()` en Angular te permite especificar múltiples estilos intermedios dentro de una sola transición. Se puede usar un `offset` opcional para definir el punto en la animación donde debe ocurrir cada cambio de estilo. -## Más sobre animaciones de Angular +## Más sobre animaciones de Angular {#more-on-angular-animations} También puede que te interese lo siguiente: diff --git a/adev-es/src/content/guide/aria/accordion.en.md b/adev-es/src/content/guide/aria/accordion.en.md index 2588aeeb..b1449e8a 100644 --- a/adev-es/src/content/guide/aria/accordion.en.md +++ b/adev-es/src/content/guide/aria/accordion.en.md @@ -145,13 +145,11 @@ Use the `ngAccordionContent` directive on an `ng-template` to defer rendering co ```angular-html
    - -
    + +
    - Description + Description
    @@ -161,91 +159,60 @@ Use the `ngAccordionContent` directive on an `ng-template` to defer rendering co By default, content remains in the DOM after the panel collapses. Set `[preserveContent]="false"` to remove the content from the DOM when the panel closes. -## APIs - -### AccordionGroup - -The container directive that manages keyboard navigation and expansion behavior for a group of accordion items. - -#### Inputs - -| Property | Type | Default | Description | -| ----------------- | --------- | ------- | ------------------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Disables all triggers in the group | -| `multiExpandable` | `boolean` | `true` | Whether multiple panels can be expanded simultaneously | -| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable. When `false`, they are skipped | -| `wrap` | `boolean` | `false` | Whether keyboard navigation wraps from last to first item and vice versa | - -#### Methods - -| Method | Parameters | Description | -| ------------- | ---------- | ---------------------------------------------------------------- | -| `expandAll` | none | Expands all panels (only works when `multiExpandable` is `true`) | -| `collapseAll` | none | Collapses all panels | - -### AccordionTrigger - -The directive applied to the button element that toggles panel visibility. - -#### Inputs - -| Property | Type | Default | Description | -| ---------- | --------- | ------- | -------------------------------------------------------------- | -| `id` | `string` | auto | Unique identifier for the trigger | -| `panelId` | `string` | — | **Required.** Must match the `panelId` of the associated panel | -| `disabled` | `boolean` | `false` | Disables this trigger | -| `expanded` | `boolean` | `false` | Whether the panel is expanded (supports two-way binding) | - -#### Signals - -| Property | Type | Description | -| -------- | ----------------- | --------------------------------------- | -| `active` | `Signal` | Whether the trigger currently has focus | - -#### Methods - -| Method | Parameters | Description | -| ---------- | ---------- | --------------------------------- | -| `expand` | none | Expands the associated panel | -| `collapse` | none | Collapses the associated panel | -| `toggle` | none | Toggles the panel expansion state | - -### AccordionPanel - -The directive applied to the element containing the collapsible content. - -#### Inputs - -| Property | Type | Default | Description | -| ----------------- | --------- | ------- | ---------------------------------------------------------------- | -| `id` | `string` | auto | Unique identifier for the panel | -| `panelId` | `string` | — | **Required.** Must match the `panelId` of the associated trigger | -| `preserveContent` | `boolean` | `true` | Whether to keep content in DOM after panel collapses | - -#### Signals - -| Property | Type | Description | -| --------- | ----------------- | --------------------------------------- | -| `visible` | `Signal` | Whether the panel is currently expanded | - -#### Methods - -| Method | Parameters | Description | -| ---------- | ---------- | --------------------------- | -| `expand` | none | Expands this panel | -| `collapse` | none | Collapses this panel | -| `toggle` | none | Toggles the expansion state | - -### AccordionContent +## Testing + +Angular Aria provides component harnesses for testing accordion components. +Here is an example of how to use the harnesses in a component test: + +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {AccordionGroupHarness} from '@angular/aria/accordion/testing'; +import {MyAccordionComponent} from './my-accordion'; // Your component + +describe('MyAccordionComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyAccordionComponent], + }); + + fixture = TestBed.createComponent(MyAccordionComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should allow expanding panels', async () => { + // Load the accordion group harness + const group = await loader.getHarness(AccordionGroupHarness); + + // Get all individual accordions (items) in the group + const accordions = await group.getAccordions(); + expect(accordions.length).toBe(3); + + // Verify initial state (first expanded, others collapsed) + expect(await accordions[0].isExpanded()).toBe(true); + expect(await accordions[1].isExpanded()).toBe(false); + + // Expand the second panel + await accordions[1].expand(); + + // Verify updated state + expect(await accordions[1].isExpanded()).toBe(true); + // If multiExpandable is false, the first one should now be collapsed + expect(await accordions[0].isExpanded()).toBe(false); + }); +}); +``` -The structural directive applied to an `ng-template` inside an accordion panel to enable lazy rendering. +## API reference -This directive has no inputs, outputs, or methods. Apply it to an `ng-template` element: +For detailed API documentation, inspect the following API references: -```angular-html -
    - - - -
    -``` +- [`AccordionGroup`](/api/aria/accordion/AccordionGroup) +- [`AccordionTrigger`](/api/aria/accordion/AccordionTrigger) +- [`AccordionPanel`](/api/aria/accordion/AccordionPanel) +- [`AccordionContent`](/api/aria/accordion/AccordionContent) diff --git a/adev-es/src/content/guide/aria/accordion.md b/adev-es/src/content/guide/aria/accordion.md index fcf8c741..d5922af9 100644 --- a/adev-es/src/content/guide/aria/accordion.md +++ b/adev-es/src/content/guide/aria/accordion.md @@ -6,7 +6,7 @@ -## Visión general +## Visión general {#overview} Un accordion organiza contenido relacionado en secciones expandibles y colapsables, reduciendo el desplazamiento de la página y ayudando a los usuarios a enfocarse en la información relevante. Cada sección tiene un botón de activación y un panel de contenido. Al hacer clic en un botón de activación se alterna la visibilidad de su panel asociado. @@ -16,7 +16,7 @@ Un accordion organiza contenido relacionado en secciones expandibles y colapsabl -## Uso +## Uso {#usage} Los accordions funcionan bien para organizar contenido en grupos lógicos donde los usuarios típicamente necesitan ver una sección a la vez. @@ -34,7 +34,7 @@ Los accordions funcionan bien para organizar contenido en grupos lógicos donde - Muestres una sola sección colapsable (usa un patrón de revelación en su lugar) - Los usuarios necesiten ver múltiples secciones simultáneamente (considera un diseño diferente) -## Características +## Características {#features} - **Modos de expansión** - Controla si uno o múltiples paneles pueden estar abiertos al mismo tiempo - **Navegación por teclado** - Navega entre botones de activación usando las teclas de flecha, Inicio y Fin @@ -44,9 +44,9 @@ Los accordions funcionan bien para organizar contenido en grupos lógicos donde - **Control programático** - Expande, colapsa o alterna paneles desde el código de tu componente - **Soporte RTL** - Soporte automático para idiomas de derecha a izquierda -## Ejemplos +## Ejemplos {#examples} -### Modo de expansión única +### Modo de expansión única {#single-expansion-mode} Establece `[multiExpandable]="false"` para permitir que solo un panel esté abierto a la vez. Abrir un nuevo panel cierra automáticamente cualquier panel previamente abierto. @@ -76,7 +76,7 @@ Establece `[multiExpandable]="false"` para permitir que solo un panel esté abie Este modo funciona bien para FAQs o situaciones donde quieres que los usuarios se enfoquen en una respuesta a la vez. -### Modo de expansión múltiple +### Modo de expansión múltiple {#multiple-expansion-mode} Establece `[multiExpandable]="true"` para permitir que múltiples paneles estén abiertos simultáneamente. Los usuarios pueden expandir tantos paneles como necesiten sin cerrar otros. @@ -108,7 +108,7 @@ Este modo es útil para secciones de formularios o cuando los usuarios necesitan NOTA: El input `multiExpandable` tiene como valor predeterminado `true`. Establécelo en `false` explícitamente si quieres el comportamiento de expansión única. -### Elementos de accordion deshabilitados +### Elementos de accordion deshabilitados {#disabled-accordion-items} Deshabilita botones de activación específicos usando el input `disabled`. Controla cómo se comportan los elementos deshabilitados durante la navegación por teclado usando el input `softDisabled` en el grupo de accordion. @@ -138,7 +138,7 @@ Deshabilita botones de activación específicos usando el input `disabled`. Cont Cuando `[softDisabled]="true"` (el valor predeterminado), los elementos deshabilitados pueden recibir foco pero no pueden activarse. Cuando `[softDisabled]="false"`, los elementos deshabilitados se omiten por completo durante la navegación por teclado. -### Renderización lazy de contenido +### Renderización lazy de contenido {#lazy-content-rendering} Usa la directiva `ngAccordionContent` en un `ng-template` para diferir la renderización del contenido hasta que el panel se expanda por primera vez. Esto mejora el rendimiento para accordions con contenido pesado como imágenes, gráficos o componentes complejos. @@ -161,91 +161,60 @@ Usa la directiva `ngAccordionContent` en un `ng-template` para diferir la render Por defecto, el contenido permanece en el DOM después de que el panel se colapsa. Establece `[preserveContent]="false"` para eliminar el contenido del DOM cuando el panel se cierra. -## APIs - -### AccordionGroup - -La directiva contenedora que gestiona la navegación por teclado y el comportamiento de expansión para un grupo de elementos de accordion. - -#### Inputs - -| Propiedad | Tipo | Predeterminado | Descripción | -| ----------------- | --------- | -------------- | ------------------------------------------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Deshabilita todos los botones de activación en el grupo | -| `multiExpandable` | `boolean` | `true` | Si múltiples paneles pueden ser expandidos simultáneamente | -| `softDisabled` | `boolean` | `true` | Cuando es `true`, los elementos deshabilitados son focalizables. Cuando es `false`, se omiten | -| `wrap` | `boolean` | `false` | Si la navegación por teclado se envuelve del último al primer elemento y viceversa | - -#### Métodos - -| Método | Parámetros | Descripción | -| ------------- | ---------- | --------------------------------------------------------------------------------- | -| `expandAll` | ninguno | Expande todos los paneles (solo funciona cuando `multiExpandable` es `true`) | -| `collapseAll` | ninguno | Colapsa todos los paneles | - -### AccordionTrigger - -La directiva aplicada al elemento button que alterna la visibilidad del panel. - -#### Inputs - -| Propiedad | Tipo | Predeterminado | Descripción | -| ---------- | --------- | -------------- | --------------------------------------------------------------------------------- | -| `id` | `string` | auto | Identificador único para el botón de activación | -| `panelId` | `string` | — | **Requerido.** Debe coincidir con el `panelId` del panel asociado | -| `disabled` | `boolean` | `false` | Deshabilita este botón de activación | -| `expanded` | `boolean` | `false` | Si el panel está expandido (soporta enlace bidireccional) | - -#### Signals - -| Propiedad | Tipo | Descripción | -| --------- | ----------------- | ---------------------------------------------------------- | -| `active` | `Signal` | Si el botón de activación actualmente tiene foco | - -#### Métodos - -| Método | Parámetros | Descripción | -| ---------- | ---------- | ------------------------------------------ | -| `expand` | ninguno | Expande el panel asociado | -| `collapse` | ninguno | Colapsa el panel asociado | -| `toggle` | ninguno | Alterna el estado de expansión del panel | - -### AccordionPanel - -La directiva aplicada al elemento que contiene el contenido colapsable. - -#### Inputs - -| Propiedad | Tipo | Predeterminado | Descripción | -| ----------------- | --------- | -------------- | -------------------------------------------------------------------------- | -| `id` | `string` | auto | Identificador único para el panel | -| `panelId` | `string` | — | **Requerido.** Debe coincidir con el `panelId` del botón de activación asociado | -| `preserveContent` | `boolean` | `true` | Si mantener el contenido en el DOM después de que el panel se colapsa | - -#### Signals - -| Propiedad | Tipo | Descripción | -| --------- | ----------------- | ------------------------------------ | -| `visible` | `Signal` | Si el panel está actualmente expandido | - -#### Métodos - -| Método | Parámetros | Descripción | -| ---------- | ---------- | ----------------------------------- | -| `expand` | ninguno | Expande este panel | -| `collapse` | ninguno | Colapsa este panel | -| `toggle` | ninguno | Alterna el estado de expansión | - -### AccordionContent +## Testing + +Angular Aria proporciona harnesses de componentes para probar componentes de accordion. +Aquí hay un ejemplo de cómo usar los harnesses en una prueba de componente: + +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {AccordionGroupHarness} from '@angular/aria/accordion/testing'; +import {MyAccordionComponent} from './my-accordion'; // Tu componente + +describe('MyAccordionComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyAccordionComponent], + }); + + fixture = TestBed.createComponent(MyAccordionComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should allow expanding panels', async () => { + // Carga el harness del grupo de accordion + const group = await loader.getHarness(AccordionGroupHarness); + + // Obtiene todos los accordions individuales (items) en el grupo + const accordions = await group.getAccordions(); + expect(accordions.length).toBe(3); + + // Verifica el estado inicial (el primero expandido, los demás colapsados) + expect(await accordions[0].isExpanded()).toBe(true); + expect(await accordions[1].isExpanded()).toBe(false); + + // Expande el segundo panel + await accordions[1].expand(); + + // Verifica el estado actualizado + expect(await accordions[1].isExpanded()).toBe(true); + // Si multiExpandable es false, el primero ahora debería estar colapsado + expect(await accordions[0].isExpanded()).toBe(false); + }); +}); +``` -La directiva estructural aplicada a un `ng-template` dentro de un panel de accordion para habilitar la renderización lazy. +## API reference -Esta directiva no tiene inputs, outputs ni métodos. Aplícala a un elemento `ng-template`: +Para documentación de API detallada, inspecciona las siguientes referencias de API: -```angular-html -
    - - - -
    -``` +- [`AccordionGroup`](/api/aria/accordion/AccordionGroup) +- [`AccordionTrigger`](/api/aria/accordion/AccordionTrigger) +- [`AccordionPanel`](/api/aria/accordion/AccordionPanel) +- [`AccordionContent`](/api/aria/accordion/AccordionContent) diff --git a/adev-es/src/content/guide/aria/autocomplete.en.md b/adev-es/src/content/guide/aria/autocomplete.en.md index 73f2ab7a..2b164bce 100644 --- a/adev-es/src/content/guide/aria/autocomplete.en.md +++ b/adev-es/src/content/guide/aria/autocomplete.en.md @@ -52,7 +52,7 @@ Angular's autocomplete provides a fully accessible combobox implementation with: - **Keyboard Navigation** - Navigate options with arrow keys, select with Enter, close with Escape - **Screen Reader Support** - Built-in ARIA attributes for assistive technologies -- **Three Filter Modes** - Choose between auto-select, manual selection, or highlighting behavior +- **Dynamic Highlight Behavior** - Built-in support for inline selection suggestions - **Signal-Based Reactivity** - Reactive state management using Angular signals - **Popover API Integration** - Leverages the native HTML Popover API for optimal positioning - **Bidirectional Text Support** - Automatically handles right-to-left (RTL) languages @@ -149,42 +149,79 @@ Highlight mode allows the user to navigate options with arrow keys without chang -## APIs +### Signal Forms Integration -### Combobox Directive +Angular Aria integrates seamlessly with the signal-based [Signal Forms](guide/forms/signals/overview) API. You can encapsulate complex inputs into reusable custom control components implementing `FormValueControl`. -The `ngCombobox` directive provides the container for autocomplete functionality. +The following example demonstrates a country selector component implementing `FormValueControl`, bound to the parent form using `[formField]` and protected by schema validation rules. -#### Inputs + + + + + + + + -| Property | Type | Default | Description | -| ------------ | ---------------------------------------------- | ---------- | ------------------------------------------------- | -| `filterMode` | `'auto-select'` \| `'manual'` \| `'highlight'` | `'manual'` | Controls selection behavior | -| `disabled` | `boolean` | `false` | Disables the combobox | -| `firstMatch` | `string` | - | The value of the first matching item in the popup | +## Testing -#### Outputs +The autocomplete pattern can be tested using a combination of `ComboboxHarness` and `ListboxHarness` from `@angular/aria/combobox/testing` and `@angular/aria/listbox/testing`. +Here is an example of how to use the harnesses to test an autocomplete component: -| Property | Type | Description | -| ---------- | ----------------- | ----------------------------------------------------- | -| `expanded` | `Signal` | Signal indicating whether the popup is currently open | +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ComboboxHarness} from '@angular/aria/combobox/testing'; +import {ListboxHarness} from '@angular/aria/listbox/testing'; +import {MyAutocompleteComponent} from './my-autocomplete'; // Your component -### ComboboxInput Directive +describe('MyAutocompleteComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -The `ngComboboxInput` directive connects an input element to the combobox. + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyAutocompleteComponent], + }); -#### Model + fixture = TestBed.createComponent(MyAutocompleteComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -| Property | Type | Description | -| -------- | -------- | ------------------------------------------------------------ | -| `value` | `string` | Two-way bindable string value of the input using `[(value)]` | + it('should filter options based on input', async () => { + const combobox = await loader.getHarness(ComboboxHarness); -### ComboboxPopupContainer Directive + // Type in the input to trigger filtering + await combobox.setValue('ap'); + expect(await combobox.isOpen()).toBe(true); -The `ngComboboxPopupContainer` directive wraps the popup content and manages its display. + // Get the listbox harness from the popup + const listbox = await combobox.getPopupWidget(ListboxHarness); + const options = await listbox.getOptions(); -Must be used with `` inside a popover element. + // Verify options are filtered (e.g., 'Apple', 'Apricot') + expect(options.length).toBe(2); + expect(await options[0].getText()).toBe('Apple'); -### Related components + // Select the first option + await options[0].click(); -Autocomplete uses [Listbox](/api/aria/listbox/Listbox) and [Option](/api/aria/listbox/Option) directives to render the suggestion list. See the [Listbox documentation](/guide/aria/listbox) for additional customization options. + // Verify the input value is updated and popup is closed + expect(await combobox.isOpen()).toBe(false); + expect(await combobox.getValue()).toBe('Apple'); + }); +}); +``` + +## API reference + +For detailed API documentation, inspect the following API references: + +- [`Combobox`](/api/aria/combobox/Combobox) +- [`ComboboxPopup`](/api/aria/combobox/ComboboxPopup) +- [`ComboboxWidget`](/api/aria/combobox/ComboboxWidget) +- [`Listbox`](/api/aria/listbox/Listbox) +- [`Option`](/api/aria/listbox/Option) diff --git a/adev-es/src/content/guide/aria/autocomplete.md b/adev-es/src/content/guide/aria/autocomplete.md index 876b4df6..0a7a290f 100644 --- a/adev-es/src/content/guide/aria/autocomplete.md +++ b/adev-es/src/content/guide/aria/autocomplete.md @@ -1,7 +1,7 @@ -## Visión general +## Visión general {#overview} Un campo de entrada accesible que filtra y sugiere opciones mientras los usuarios escriben, ayudándoles a encontrar y seleccionar valores de una lista. @@ -31,7 +31,7 @@ Un campo de entrada accesible que filtra y sugiere opciones mientras los usuario -## Uso +## Uso {#usage} Autocomplete funciona mejor cuando los usuarios necesitan seleccionar de un conjunto grande de opciones donde escribir es más rápido que desplazarse. Considera usar autocomplete cuando: @@ -46,7 +46,7 @@ Evita autocomplete cuando: - Los usuarios necesitan explorar opciones - Si el descubrimiento es importante, muestra todas las opciones de frente - Las opciones son desconocidas - Los usuarios no pueden escribir lo que no saben que existe en la lista -## Características +## Características {#features} El autocomplete de Angular proporciona una implementación de combobox totalmente accesible con: @@ -57,9 +57,9 @@ El autocomplete de Angular proporciona una implementación de combobox totalment - **Integración con Popover API** - Aprovecha la Popover API nativa de HTML para posicionamiento óptimo - **Soporte para Texto Bidireccional** - Maneja automáticamente idiomas de derecha a izquierda (RTL) -## Ejemplos +## Ejemplos {#examples} -### Modo de auto-selección +### Modo de auto-selección {#auto-select-mode} Los usuarios que escriben texto parcial esperan confirmación inmediata de que su entrada coincide con una opción disponible. El modo de auto-selección actualiza el valor de entrada para coincidir con la primera opción filtrada mientras los usuarios escriben, reduciendo el número de pulsaciones de teclas necesarias y proporcionando retroalimentación instantánea de que su búsqueda va por buen camino. @@ -89,7 +89,7 @@ Los usuarios que escriben texto parcial esperan confirmación inmediata de que s -### Modo de selección manual +### Modo de selección manual {#manual-selection-mode} El modo de selección manual mantiene el texto escrito sin cambios mientras los usuarios navegan la lista de sugerencias, previniendo confusión por actualizaciones automáticas. La entrada solo cambia cuando los usuarios confirman explícitamente su elección con Enter o un clic. @@ -119,7 +119,7 @@ El modo de selección manual mantiene el texto escrito sin cambios mientras los -### Modo de resaltado +### Modo de resaltado {#highlight-mode} El modo de resaltado permite al usuario navegar opciones con teclas de flecha sin cambiar el valor de entrada mientras explora hasta que selecciona explícitamente una nueva opción con Enter o clic. @@ -149,42 +149,79 @@ El modo de resaltado permite al usuario navegar opciones con teclas de flecha si -## APIs +### Integración con Signal Forms {#signal-forms-integration} -### Directiva Combobox +Angular Aria se integra sin problemas con la API de [Signal Forms](guide/forms/signals/overview) basada en signals. Puedes encapsular inputs complejos en componentes de control personalizados reutilizables implementando `FormValueControl`. -La directiva `ngCombobox` proporciona el contenedor para la funcionalidad de autocomplete. +El siguiente ejemplo demuestra un componente selector de país que implementa `FormValueControl`, enlazado al formulario padre usando `[formField]` y protegido por reglas de validación de schema. -#### Inputs + + + + + + + + -| Propiedad | Tipo | Predeterminado | Descripción | -| ------------ | ---------------------------------------------- | -------------- | ------------------------------------------------- | -| `filterMode` | `'auto-select'` \| `'manual'` \| `'highlight'` | `'manual'` | Controla el comportamiento de selección | -| `disabled` | `boolean` | `false` | Deshabilita el combobox | -| `firstMatch` | `string` | - | El valor del primer elemento coincidente en el popup | +## Testing -#### Outputs +El patrón autocomplete puede probarse usando una combinación de `ComboboxHarness` y `ListboxHarness` de `@angular/aria/combobox/testing` y `@angular/aria/listbox/testing`. +Aquí hay un ejemplo de cómo usar los harnesses para probar un componente autocomplete: -| Propiedad | Tipo | Descripción | -| ---------- | ----------------- | ---------------------------------------------------------- | -| `expanded` | `Signal` | Signal que indica si el popup está actualmente abierto | +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ComboboxHarness} from '@angular/aria/combobox/testing'; +import {ListboxHarness} from '@angular/aria/listbox/testing'; +import {MyAutocompleteComponent} from './my-autocomplete'; // Tu componente -### Directiva ComboboxInput +describe('MyAutocompleteComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -La directiva `ngComboboxInput` conecta un elemento input al combobox. + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyAutocompleteComponent], + }); -#### Modelo + fixture = TestBed.createComponent(MyAutocompleteComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -| Propiedad | Tipo | Descripción | -| --------- | -------- | --------------------------------------------------------------------------- | -| `value` | `string` | Valor string enlazable bidireccionalmente de la entrada usando `[(value)]` | + it('should filter options based on input', async () => { + const combobox = await loader.getHarness(ComboboxHarness); -### Directiva ComboboxPopupContainer + // Escribe en el input para activar el filtrado + await combobox.setValue('ap'); + expect(await combobox.isOpen()).toBe(true); -La directiva `ngComboboxPopupContainer` envuelve el contenido del popup y gestiona su visualización. + // Obtiene el harness del listbox desde el popup + const listbox = await combobox.getPopupWidget(ListboxHarness); + const options = await listbox.getOptions(); -Debe usarse con `` dentro de un elemento popover. + // Verifica que las opciones estén filtradas (ej. 'Apple', 'Apricot') + expect(options.length).toBe(2); + expect(await options[0].getText()).toBe('Apple'); -### Componentes relacionados + // Selecciona la primera opción + await options[0].click(); -Autocomplete usa las directivas [Listbox](/api/aria/listbox/Listbox) y [Option](/api/aria/listbox/Option) para renderizar la lista de sugerencias. Consulta la [documentación de Listbox](/guide/aria/listbox) para opciones de personalización adicionales. + // Verifica que el valor del input se actualizó y el popup está cerrado + expect(await combobox.isOpen()).toBe(false); + expect(await combobox.getValue()).toBe('Apple'); + }); +}); +``` + +## API reference + +Para documentación de API detallada, inspecciona las siguientes referencias de API: + +- [`Combobox`](/api/aria/combobox/Combobox) +- [`ComboboxPopup`](/api/aria/combobox/ComboboxPopup) +- [`ComboboxWidget`](/api/aria/combobox/ComboboxWidget) +- [`Listbox`](/api/aria/listbox/Listbox) +- [`Option`](/api/aria/listbox/Option) diff --git a/adev-es/src/content/guide/aria/combobox.en.md b/adev-es/src/content/guide/aria/combobox.en.md index 71390217..aa2aa426 100644 --- a/adev-es/src/content/guide/aria/combobox.en.md +++ b/adev-es/src/content/guide/aria/combobox.en.md @@ -8,42 +8,42 @@ ## Overview -A directive that coordinates a text input with a popup, providing the primitive directive for autocomplete, select, and multiselect patterns. +A directive that coordinates a trigger element (such as a text input, button, or `div`) with a popup, providing the primitive directive for autocomplete, select, and multiselect patterns. - - - - + + + + - - - - + + + + - - - - + + + + ## Usage -Combobox is the primitive directive that coordinates a text input with a popup. It provides the foundation for autocomplete, select, and multiselect patterns. Consider using combobox directly when: +Combobox is the primitive directive that coordinates an interactive trigger element (such as a text input, button, or `div`) with a popup. It provides the foundation for autocomplete, select, and multiselect patterns. Consider using combobox directly when: - **Building custom autocomplete patterns** - Creating specialized filtering or suggestion behavior - **Creating custom selection components** - Developing dropdowns with unique requirements - **Coordinating input with popup** - Pairing text input with listbox, tree, or dialog content -- **Implementing specific filter modes** - Using manual, auto-select, or highlight behaviors +- **Implementing custom filtering** - Filtering and orchestrating matching options in user space Use documented patterns instead when: @@ -51,14 +51,14 @@ Use documented patterns instead when: - Single-selection dropdowns are needed - See the [Select pattern](guide/aria/select) for complete dropdown implementation - Multiple-selection dropdowns are needed - See the [Multiselect pattern](guide/aria/multiselect) for multi-select with compact display -Note: The [Autocomplete](guide/aria/autocomplete), [Select](guide/aria/select), and [Multiselect](guide/aria/multiselect) guides show documented patterns that combine this directive with [Listbox](guide/aria/listbox) for specific use cases. +NOTE: The [Autocomplete](guide/aria/autocomplete), [Select](guide/aria/select), and [Multiselect](guide/aria/multiselect) guides show documented patterns that combine this directive with [Listbox](guide/aria/listbox) for specific use cases. ## Features Angular's combobox provides a fully accessible input-popup coordination system with: -- **Text Input with Popup** - Coordinates input field with popup content -- **Three Filter Modes** - Manual, auto-select, or highlight behaviors +- **Trigger Element with Popup** - Coordinates trigger element with popup content +- **Flexible Coordination** - Integrates seamlessly with standard layouts (listbox, tree, grid, or dialog) - **Keyboard Navigation** - Arrow keys, Enter, Escape handling - **Screen Reader Support** - Built-in ARIA attributes including role="combobox" and aria-expanded - **Popup Management** - Automatic show/hide based on user interaction @@ -96,7 +96,7 @@ An accessible input field that filters and suggests options as users type, helpi -The `filterMode="manual"` setting gives complete control over filtering and selection. The input updates a signal that filters the options list. Users navigate with arrow keys and select with Enter or click. This mode provides the most flexibility for custom filtering logic. See the [Autocomplete guide](guide/aria/autocomplete) for complete filtering patterns and examples. +Filtering is managed in user space by updating a signal that reactively filters the options list. Users navigate with arrow keys and select with Enter or click. This provides complete control and maximum flexibility for custom selection logic. See the [Autocomplete guide](guide/aria/autocomplete) for complete filtering patterns and examples. ### Readonly mode @@ -128,13 +128,43 @@ A pattern that combines a readonly combobox with listbox to create single-select -The `readonly` attribute prevents typing in the input field. The popup opens on click or arrow keys. Users navigate options with keyboard and select with Enter or click. +Triggering a dropdown without text input can be achieved using a button as the host trigger, or applying the native HTML `readonly` attribute to the input trigger. The popup opens on click or arrow keys. This configuration provides the foundation for the [Select](guide/aria/select) and [Multiselect](guide/aria/multiselect) patterns. See those guides for complete dropdown implementations with triggers and overlay positioning. +### Datepicker grid + +Combobox can coordinate with a two-dimensional grid to create accessible datepickers. Users navigate dates inside the calendar grid table using directional arrow keys and confirm selection with click, Enter, or Spacebar. + + + + + + + + + + + + + + + + + + + + + + + + + + + ### Dialog popup -Popups sometimes need modal behavior with a backdrop and focus trap. The combobox dialog directive provides this pattern for specialized use cases. +Dialog popups combine the combobox trigger with standard dialog layouts and focus traps (such as CDK's `cdkTrapFocus`). Use dialog popups when the overlay requires modal behavior or backdrop interaction. @@ -162,94 +192,66 @@ Popups sometimes need modal behavior with a backdrop and focus trap. The combobo -The `ngComboboxDialog` directive creates a modal popup using the native dialog element. This provides backdrop behavior and focus trapping. Use dialog popups when the selection interface requires modal interaction or when the popup content is complex enough to warrant full-screen focus. - -## APIs - -### Combobox Directive - -The `ngCombobox` directive coordinates a text input with a popup. - -#### Inputs - -| Property | Type | Default | Description | -| ---------------- | ---------------------------------------------- | ---------- | ------------------------------------------------ | -| `filterMode` | `'manual'` \| `'auto-select'` \| `'highlight'` | `'manual'` | Controls selection behavior | -| `disabled` | `boolean` | `false` | Disables the combobox | -| `readonly` | `boolean` | `false` | Makes combobox readonly (for Select/Multiselect) | -| `firstMatch` | `V` | - | Value of first matching item for auto-select | -| `alwaysExpanded` | `boolean` | `false` | Keeps popup always open | +## Testing -**Filter Modes:** +Angular Aria provides a `ComboboxHarness` for testing combobox components. +Here is an example of how to use the harness in a component test: -- **`'manual'`** - User controls filtering and selection explicitly. The popup shows options based on your filtering logic. Users select with Enter or click. This mode provides the most flexibility. -- **`'auto-select'`** - Input value automatically updates to the first matching option as users type. Requires the `firstMatch` input for coordination. See the [Autocomplete guide](guide/aria/autocomplete#auto-select-mode) for examples. -- **`'highlight'`** - Highlights matching text without changing the input value. Users navigate with arrow keys and select with Enter. +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ComboboxHarness} from '@angular/aria/combobox/testing'; +import {MyComboboxComponent} from './my-combobox'; // Your component -#### Signals +describe('MyComboboxComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -| Property | Type | Description | -| ---------- | ----------------- | ------------------------------- | -| `expanded` | `Signal` | Whether popup is currently open | + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyComboboxComponent], + }); -#### Methods + fixture = TestBed.createComponent(MyComboboxComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -| Method | Parameters | Description | -| ---------- | ---------- | ---------------------- | -| `open` | none | Opens the combobox | -| `close` | none | Closes the combobox | -| `expand` | none | Expands the combobox | -| `collapse` | none | Collapses the combobox | + it('should allow opening and closing the popup', async () => { + const combobox = await loader.getHarness(ComboboxHarness); -### ComboboxInput Directive + // Verify initial state + expect(await combobox.isOpen()).toBe(false); -The `ngComboboxInput` directive connects an input element to the combobox. + // Open the popup + await combobox.open(); + expect(await combobox.isOpen()).toBe(true); -#### Model - -| Property | Type | Description | -| -------- | -------- | ---------------------------------------- | -| `value` | `string` | Two-way bindable value using `[(value)]` | - -The input element receives keyboard handling and ARIA attributes automatically. - -### ComboboxPopup Directive - -The `ngComboboxPopup` directive (host directive) manages popup visibility and coordination. Typically used with `ngComboboxPopupContainer` in an `ng-template` or with CDK Overlay. - -### ComboboxPopupContainer Directive - -The `ngComboboxPopupContainer` directive marks an `ng-template` as the popup content. - -```html - -
    ...
    -
    + // Close the popup + await combobox.close(); + expect(await combobox.isOpen()).toBe(false); + }); +}); ``` -Used with Popover API or CDK Overlay for positioning. - -### ComboboxDialog Directive +## API reference -The `ngComboboxDialog` directive creates a modal combobox popup. - -```html - -
    ...
    -
    -``` +For detailed API documentation, inspect the following API references: -Use for modal popup behavior with backdrop and focus trap. +- [`Combobox`](/api/aria/combobox/Combobox) +- [`ComboboxPopup`](/api/aria/combobox/ComboboxPopup) +- [`ComboboxWidget`](/api/aria/combobox/ComboboxWidget) ### Related patterns and directives Combobox is the primitive directive for these documented patterns: -- **[Autocomplete](guide/aria/autocomplete)** - Filtering and suggestions pattern (uses Combobox with filter modes) -- **[Select](guide/aria/select)** - Single selection dropdown pattern (uses Combobox with `readonly`) -- **[Multiselect](guide/aria/multiselect)** - Multiple selection pattern (uses Combobox with `readonly` + multi-enabled Listbox) +- [Autocomplete](guide/aria/autocomplete) - Filtering and suggestions pattern (coordinates input typing with options list) +- [Select](guide/aria/select) - Single selection dropdown pattern (applied directly on non-editable button triggers) +- [Multiselect](guide/aria/multiselect) - Multiple selection pattern (applied on non-editable triggers with multi-enabled Listbox) Combobox typically combines with: -- **[Listbox](guide/aria/listbox)** - Most common popup content -- **[Tree](guide/aria/tree)** - Hierarchical popup content (see Tree guide for examples) +- [Listbox](guide/aria/listbox) - Most common popup content +- [Tree](guide/aria/tree) - Hierarchical popup content (see Tree guide for examples) diff --git a/adev-es/src/content/guide/aria/combobox.md b/adev-es/src/content/guide/aria/combobox.md index 4b118fd6..667a43e1 100644 --- a/adev-es/src/content/guide/aria/combobox.md +++ b/adev-es/src/content/guide/aria/combobox.md @@ -6,7 +6,7 @@ -## Visión general +## Visión general {#overview} Una directiva que coordina un campo de entrada de texto con un popup, proporcionando la directiva primitiva para patrones de autocomplete, select y multiselect. @@ -36,7 +36,7 @@ Una directiva que coordina un campo de entrada de texto con un popup, proporcion -## Uso +## Uso {#usage} Combobox es la directiva primitiva que coordina un campo de entrada de texto con un popup. Proporciona la base para patrones de autocomplete, select y multiselect. Considera usar combobox directamente cuando: @@ -53,7 +53,7 @@ Usa patrones documentados en su lugar cuando: Nota: Las guías de [Autocomplete](guide/aria/autocomplete), [Select](guide/aria/select) y [Multiselect](guide/aria/multiselect) muestran patrones documentados que combinan esta directiva con [Listbox](guide/aria/listbox) para casos de uso específicos. -## Características +## Características {#features} El combobox de Angular proporciona un sistema de coordinación entrada-popup totalmente accesible con: @@ -64,7 +64,7 @@ El combobox de Angular proporciona un sistema de coordinación entrada-popup tot - **Gestión de Popup** - Mostrar/ocultar automático basado en interacción del usuario - **Reactividad Basada en Signals** - Gestión de estado reactiva usando signals de Angular -## Ejemplos +## Ejemplos {#examples} ### Autocomplete @@ -98,7 +98,7 @@ Un campo de entrada accesible que filtra y sugiere opciones mientras los usuario La configuración `filterMode="manual"` proporciona control completo sobre el filtrado y la selección. La entrada actualiza un signal que filtra la lista de opciones. Los usuarios navegan con teclas de flecha y seleccionan con Enter o clic. Este modo proporciona la mayor flexibilidad para lógica de filtrado personalizada. Consulta la [guía de Autocomplete](guide/aria/autocomplete) para patrones de filtrado completos y ejemplos. -### Modo de solo lectura +### Modo de solo lectura {#readonly-mode} Un patrón que combina un combobox de solo lectura con listbox para crear menús desplegables de selección única con navegación por teclado y soporte para lectores de pantalla. @@ -132,7 +132,37 @@ El atributo `readonly` previene la escritura en el campo de entrada. El popup se Esta configuración proporciona la base para los patrones de [Select](guide/aria/select) y [Multiselect](guide/aria/multiselect). Consulta esas guías para implementaciones completas de menú desplegable con activadores y posicionamiento de overlay. -### Popup de diálogo +### Grid de datepicker {#datepicker-grid} + +Combobox puede coordinarse con un grid bidimensional para crear datepickers accesibles. Los usuarios navegan fechas dentro de la tabla de grid del calendario usando las teclas de flecha direccionales y confirman la selección con clic, Enter o barra espaciadora. + + + + + + + + + + + + + + + + + + + + + + + + + + + +### Popup de diálogo {#dialog-popup} Los popups a veces necesitan comportamiento modal con un fondo y trampa de foco. La directiva de diálogo de combobox proporciona este patrón para casos de uso especializados. @@ -164,92 +194,66 @@ Los popups a veces necesitan comportamiento modal con un fondo y trampa de foco. La directiva `ngComboboxDialog` crea un popup modal usando el elemento dialog nativo. Esto proporciona comportamiento de fondo y trampa de foco. Usa popups de diálogo cuando la interfaz de selección requiere interacción modal o cuando el contenido del popup es lo suficientemente complejo como para justificar foco a pantalla completa. -## APIs - -### Directiva Combobox - -La directiva `ngCombobox` coordina un campo de entrada de texto con un popup. - -#### Inputs - -| Propiedad | Tipo | Predeterminado | Descripción | -| ---------------- | ---------------------------------------------- | -------------- | ---------------------------------------------------- | -| `filterMode` | `'manual'` \| `'auto-select'` \| `'highlight'` | `'manual'` | Controla el comportamiento de selección | -| `disabled` | `boolean` | `false` | Deshabilita el combobox | -| `readonly` | `boolean` | `false` | Hace el combobox de solo lectura (para Select/Multiselect) | -| `firstMatch` | `V` | - | Valor del primer elemento coincidente para auto-selección | -| `alwaysExpanded` | `boolean` | `false` | Mantiene el popup siempre abierto | - -**Modos de Filtro:** - -- **`'manual'`** - El usuario controla el filtrado y selección explícitamente. El popup muestra opciones basadas en tu lógica de filtrado. Los usuarios seleccionan con Enter o clic. Este modo proporciona la mayor flexibilidad. -- **`'auto-select'`** - El valor de entrada se actualiza automáticamente a la primera opción coincidente mientras los usuarios escriben. Requiere el input `firstMatch` para coordinación. Consulta la [guía de Autocomplete](guide/aria/autocomplete#auto-select-mode) para ejemplos. -- **`'highlight'`** - Resalta texto coincidente sin cambiar el valor de entrada. Los usuarios navegan con teclas de flecha y seleccionan con Enter. - -#### Signals +## Testing -| Propiedad | Tipo | Descripción | -| ---------- | ----------------- | ------------------------------------ | -| `expanded` | `Signal` | Si el popup está actualmente abierto | +Angular Aria proporciona un `ComboboxHarness` para probar componentes combobox. +Aquí hay un ejemplo de cómo usar el harness en una prueba de componente: -#### Métodos +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ComboboxHarness} from '@angular/aria/combobox/testing'; +import {MyComboboxComponent} from './my-combobox'; // Tu componente -| Método | Parámetros | Descripción | -| ---------- | ---------- | ---------------------- | -| `open` | ninguno | Abre el combobox | -| `close` | ninguno | Cierra el combobox | -| `expand` | ninguno | Expande el combobox | -| `collapse` | ninguno | Colapsa el combobox | +describe('MyComboboxComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -### Directiva ComboboxInput + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyComboboxComponent], + }); -La directiva `ngComboboxInput` conecta un elemento input al combobox. + fixture = TestBed.createComponent(MyComboboxComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -#### Modelo + it('should allow opening and closing the popup', async () => { + const combobox = await loader.getHarness(ComboboxHarness); -| Propiedad | Tipo | Descripción | -| --------- | -------- | ----------------------------------------------------------- | -| `value` | `string` | Valor enlazable bidireccionalmente usando `[(value)]` | + // Verifica el estado inicial + expect(await combobox.isOpen()).toBe(false); -El elemento input recibe manejo de teclado y atributos ARIA automáticamente. + // Abre el popup + await combobox.open(); + expect(await combobox.isOpen()).toBe(true); -### Directiva ComboboxPopup - -La directiva `ngComboboxPopup` (directiva host) gestiona la visibilidad y coordinación del popup. Típicamente usada con `ngComboboxPopupContainer` en un `ng-template` o con CDK Overlay. - -### Directiva ComboboxPopupContainer - -La directiva `ngComboboxPopupContainer` marca un `ng-template` como el contenido del popup. - -```html - -
    ...
    -
    + // Cierra el popup + await combobox.close(); + expect(await combobox.isOpen()).toBe(false); + }); +}); ``` -Usada con Popover API o CDK Overlay para posicionamiento. +## API reference -### Directiva ComboboxDialog - -La directiva `ngComboboxDialog` crea un popup de combobox modal. - -```html - -
    ...
    -
    -``` +Para documentación de API detallada, inspecciona las siguientes referencias de API: -Usa para comportamiento de popup modal con fondo y trampa de foco. +- [`Combobox`](/api/aria/combobox/Combobox) +- [`ComboboxPopup`](/api/aria/combobox/ComboboxPopup) +- [`ComboboxWidget`](/api/aria/combobox/ComboboxWidget) -### Patrones y directivas relacionados +### Patrones y directivas relacionados {#related-patterns-and-directives} Combobox es la directiva primitiva para estos patrones documentados: -- **[Autocomplete](guide/aria/autocomplete)** - Patrón de filtrado y sugerencias (usa Combobox con modos de filtro) -- **[Select](guide/aria/select)** - Patrón de menú desplegable de selección única (usa Combobox con `readonly`) -- **[Multiselect](guide/aria/multiselect)** - Patrón de selección múltiple (usa Combobox con `readonly` + Listbox multi-habilitado) +- [Autocomplete](guide/aria/autocomplete) - Patrón de filtrado y sugerencias (coordina la escritura del input con la lista de opciones) +- [Select](guide/aria/select) - Patrón de menú desplegable de selección única (aplicado directamente en triggers de botón no editables) +- [Multiselect](guide/aria/multiselect) - Patrón de selección múltiple (aplicado en triggers no editables con Listbox multi-habilitado) Combobox típicamente se combina con: -- **[Listbox](guide/aria/listbox)** - Contenido de popup más común -- **[Tree](guide/aria/tree)** - Contenido de popup jerárquico (consulta la guía de Tree para ejemplos) +- [Listbox](guide/aria/listbox) - Contenido de popup más común +- [Tree](guide/aria/tree) - Contenido de popup jerárquico (consulta la guía de Tree para ejemplos) diff --git a/adev-es/src/content/guide/aria/grid.en.md b/adev-es/src/content/guide/aria/grid.en.md index 2b162aee..7a241fd4 100644 --- a/adev-es/src/content/guide/aria/grid.en.md +++ b/adev-es/src/content/guide/aria/grid.en.md @@ -135,11 +135,13 @@ Instead of tabbing through each button, users navigate with arrow keys and only Enable selection with `[enableSelection]="true"` and configure how focus and selection interact. ```angular-html - +
    @@ -157,58 +159,63 @@ Enable selection with `[enableSelection]="true"` and configure how focus and sel - `roving`: Focus moves to cells using `tabindex` (better for simple grids) - `activedescendant`: Focus stays on grid container, `aria-activedescendant` indicates active cell (better for virtual scrolling) -## APIs - -### Grid - -The container directive that provides keyboard navigation and focus management for rows and cells. - -#### Inputs - -| Property | Type | Default | Description | -| ---------------------- | ------------------------------------ | ---------- | ------------------------------------------------------------- | -| `enableSelection` | `boolean` | `false` | Whether selection is enabled for the grid | -| `disabled` | `boolean` | `false` | Disables the entire grid | -| `softDisabled` | `boolean` | `true` | When `true`, disabled cells are focusable but not interactive | -| `focusMode` | `'roving' \| 'activedescendant'` | `'roving'` | Focus strategy used by the grid | -| `rowWrap` | `'continuous' \| 'loop' \| 'nowrap'` | `'loop'` | Navigation wrapping behavior along rows | -| `colWrap` | `'continuous' \| 'loop' \| 'nowrap'` | `'loop'` | Navigation wrapping behavior along columns | -| `multi` | `boolean` | `false` | Whether multiple cells can be selected | -| `selectionMode` | `'follow' \| 'explicit'` | `'follow'` | Whether selection follows focus or requires explicit action | -| `enableRangeSelection` | `boolean` | `false` | Enable range selections with modifier keys or dragging | - -### GridRow - -Represents a row within a grid and serves as a container for grid cells. - -#### Inputs - -| Property | Type | Default | Description | -| ---------- | -------- | ------- | ------------------------------------- | -| `rowIndex` | `number` | auto | The index of this row within the grid | - -### GridCell - -Represents an individual cell within a grid row. - -#### Inputs +## Testing + +Angular Aria provides component harnesses for testing grid components. +Here is an example of how to use the harnesses in a component test: + +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {GridHarness} from '@angular/aria/grid/testing'; +import {MyGridComponent} from './my-grid'; // Your component + +describe('MyGridComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyGridComponent], + }); + + fixture = TestBed.createComponent(MyGridComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should read cell values and focus cells', async () => { + const grid = await loader.getHarness(GridHarness); + + // Get all cells text in a 2D array organized by rows + const cellTexts = await grid.getCellTextByIndex(); + expect(cellTexts).toEqual([ + ['Cell 1.1', 'Cell 1.2'], + ['Cell 2.1', 'Cell 2.2'], + ]); + + // Get a specific cell by text + const cells = await grid.getCells({text: 'Cell 1.1'}); + expect(cells.length).toBe(1); + const cell = cells[0]; + + // Verify cell state + expect(await cell.isSelected()).toBe(true); + expect(await cell.isActive()).toBe(true); + + // Focus the cell + await cell.focus(); + expect(await cell.isFocused()).toBe(true); + }); +}); +``` -| Property | Type | Default | Description | -| ------------- | ---------------------------- | -------------- | ------------------------------------------------------- | -| `id` | `string` | auto | Unique identifier for the cell | -| `role` | `string` | `'gridcell'` | Cell role: `gridcell`, `columnheader`, or `rowheader` | -| `disabled` | `boolean` | `false` | Disables this cell | -| `selected` | `boolean` | `false` | Whether the cell is selected (supports two-way binding) | -| `selectable` | `boolean` | `true` | Whether the cell can be selected | -| `rowSpan` | `number` | — | Number of rows the cell spans | -| `colSpan` | `number` | — | Number of columns the cell spans | -| `rowIndex` | `number` | — | Row index of the cell | -| `colIndex` | `number` | — | Column index of the cell | -| `orientation` | `'vertical' \| 'horizontal'` | `'horizontal'` | Orientation for widgets within the cell | -| `wrap` | `boolean` | `true` | Whether widget navigation wraps within the cell | +## API reference -#### Signals +For detailed API documentation, inspect the following API references: -| Property | Type | Description | -| -------- | ----------------- | ------------------------------------ | -| `active` | `Signal` | Whether the cell currently has focus | +- [`Grid`](/api/aria/grid/Grid) +- [`GridRow`](/api/aria/grid/GridRow) +- [`GridCell`](/api/aria/grid/GridCell) +- [`GridCellWidget`](/api/aria/grid/GridCellWidget) diff --git a/adev-es/src/content/guide/aria/grid.md b/adev-es/src/content/guide/aria/grid.md index 4b31ac63..b3b88655 100644 --- a/adev-es/src/content/guide/aria/grid.md +++ b/adev-es/src/content/guide/aria/grid.md @@ -6,7 +6,7 @@ -## Visión general +## Visión general {#overview} Un grid permite a los usuarios navegar datos bidimensionales o elementos interactivos usando teclas de flecha direccionales, Inicio, Fin y Página Arriba/Abajo. Los grids funcionan para tablas de datos, calendarios, hojas de cálculo y patrones de diseño que agrupan elementos interactivos relacionados. @@ -16,7 +16,7 @@ Un grid permite a los usuarios navegar datos bidimensionales o elementos interac -## Uso +## Uso {#usage} Los grids funcionan bien para datos o elementos interactivos organizados en filas y columnas donde los usuarios necesitan navegación por teclado en múltiples direcciones. @@ -35,7 +35,7 @@ Los grids funcionan bien para datos o elementos interactivos organizados en fila - Muestres datos jerárquicos (usa [Tree](guide/aria/tree) en su lugar) - Construyas formularios sin diseño tabular (usa controles de formulario estándar) -## Características +## Características {#features} - **Navegación bidimensional** - Las teclas de flecha se mueven entre celdas en todas las direcciones - **Modos de foco** - Elige entre estrategias de foco de roving tabindex o activedescendant @@ -45,9 +45,9 @@ Los grids funcionan bien para datos o elementos interactivos organizados en fila - **Estados deshabilitados** - Deshabilita el grid completo o celdas individuales - **Soporte RTL** - Navegación automática para idiomas de derecha a izquierda -## Ejemplos +## Ejemplos {#examples} -### Grid de tabla de datos +### Grid de tabla de datos {#data-table-grid} Usa un grid para tablas interactivas donde los usuarios necesitan navegar entre celdas usando teclas de flecha. Este ejemplo muestra una tabla de datos básica con navegación por teclado. @@ -70,7 +70,7 @@ Usa un grid para tablas interactivas donde los usuarios necesitan navegar entre Aplica la directiva `ngGrid` al elemento table, `ngGridRow` a cada fila y `ngGridCell` a cada celda. -### Grid de calendario +### Grid de calendario {#calendar-grid} Los calendarios son un caso de uso común para grids. Este ejemplo muestra una vista de mes donde los usuarios navegan fechas usando teclas de flecha. @@ -100,7 +100,7 @@ Los calendarios son un caso de uso común para grids. Este ejemplo muestra una v Los usuarios pueden activar una fecha presionando Enter o Espacio cuando están enfocados en una celda. -### Grid de diseño +### Grid de diseño {#layout-grid} Usa un grid de diseño para agrupar elementos interactivos y reducir las paradas de tabulación. Este ejemplo muestra un grid de botones tipo píldora. @@ -130,7 +130,7 @@ Usa un grid de diseño para agrupar elementos interactivos y reducir las paradas En lugar de tabular a través de cada botón, los usuarios navegan con teclas de flecha y solo un botón recibe foco de tabulación. -### Modos de selección y foco +### Modos de selección y foco {#selection-and-focus-modes} Habilita la selección con `[enableSelection]="true"` y configura cómo interactúan el foco y la selección. @@ -157,58 +157,63 @@ Habilita la selección con `[enableSelection]="true"` y configura cómo interact - `roving`: El foco se mueve a las celdas usando `tabindex` (mejor para grids simples) - `activedescendant`: El foco permanece en el contenedor del grid, `aria-activedescendant` indica la celda activa (mejor para desplazamiento virtual) -## APIs - -### Grid - -La directiva contenedora que proporciona navegación por teclado y gestión de foco para filas y celdas. - -#### Inputs - -| Propiedad | Tipo | Predeterminado | Descripción | -| ---------------------- | ------------------------------------ | -------------- | --------------------------------------------------------------------------------------- | -| `enableSelection` | `boolean` | `false` | Si la selección está habilitada para el grid | -| `disabled` | `boolean` | `false` | Deshabilita el grid completo | -| `softDisabled` | `boolean` | `true` | Cuando es `true`, las celdas deshabilitadas son focalizables pero no interactivas | -| `focusMode` | `'roving' \| 'activedescendant'` | `'roving'` | Estrategia de foco usada por el grid | -| `rowWrap` | `'continuous' \| 'loop' \| 'nowrap'` | `'loop'` | Comportamiento de envoltura de navegación a lo largo de las filas | -| `colWrap` | `'continuous' \| 'loop' \| 'nowrap'` | `'loop'` | Comportamiento de envoltura de navegación a lo largo de las columnas | -| `multi` | `boolean` | `false` | Si múltiples celdas pueden ser seleccionadas | -| `selectionMode` | `'follow' \| 'explicit'` | `'follow'` | Si la selección sigue al foco o requiere acción explícita | -| `enableRangeSelection` | `boolean` | `false` | Habilita selecciones de rango con teclas modificadoras o arrastrando | - -### GridRow - -Representa una fila dentro de un grid y sirve como contenedor para celdas de grid. - -#### Inputs - -| Propiedad | Tipo | Predeterminado | Descripción | -| ---------- | -------- | -------------- | ------------------------------------- | -| `rowIndex` | `number` | auto | El índice de esta fila dentro del grid | - -### GridCell - -Representa una celda individual dentro de una fila de grid. - -#### Inputs +## Testing + +Angular Aria proporciona harnesses de componentes para probar componentes grid. +Aquí hay un ejemplo de cómo usar los harnesses en una prueba de componente: + +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {GridHarness} from '@angular/aria/grid/testing'; +import {MyGridComponent} from './my-grid'; // Tu componente + +describe('MyGridComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyGridComponent], + }); + + fixture = TestBed.createComponent(MyGridComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should read cell values and focus cells', async () => { + const grid = await loader.getHarness(GridHarness); + + // Obtiene el texto de todas las celdas en un array 2D organizado por filas + const cellTexts = await grid.getCellTextByIndex(); + expect(cellTexts).toEqual([ + ['Cell 1.1', 'Cell 1.2'], + ['Cell 2.1', 'Cell 2.2'], + ]); + + // Obtiene una celda específica por texto + const cells = await grid.getCells({text: 'Cell 1.1'}); + expect(cells.length).toBe(1); + const cell = cells[0]; + + // Verifica el estado de la celda + expect(await cell.isSelected()).toBe(true); + expect(await cell.isActive()).toBe(true); + + // Enfoca la celda + await cell.focus(); + expect(await cell.isFocused()).toBe(true); + }); +}); +``` -| Propiedad | Tipo | Predeterminado | Descripción | -| ------------- | ---------------------------- | -------------- | --------------------------------------------------------------------------- | -| `id` | `string` | auto | Identificador único para la celda | -| `role` | `string` | `'gridcell'` | Rol de la celda: `gridcell`, `columnheader` o `rowheader` | -| `disabled` | `boolean` | `false` | Deshabilita esta celda | -| `selected` | `boolean` | `false` | Si la celda está seleccionada (soporta enlace bidireccional) | -| `selectable` | `boolean` | `true` | Si la celda puede ser seleccionada | -| `rowSpan` | `number` | — | Número de filas que abarca la celda | -| `colSpan` | `number` | — | Número de columnas que abarca la celda | -| `rowIndex` | `number` | — | Índice de fila de la celda | -| `colIndex` | `number` | — | Índice de columna de la celda | -| `orientation` | `'vertical' \| 'horizontal'` | `'horizontal'` | Orientación para widgets dentro de la celda | -| `wrap` | `boolean` | `true` | Si la navegación de widgets se envuelve dentro de la celda | +## API reference -#### Signals +Para documentación de API detallada, inspecciona las siguientes referencias de API: -| Propiedad | Tipo | Descripción | -| --------- | ----------------- | ---------------------------------------- | -| `active` | `Signal` | Si la celda actualmente tiene foco | +- [`Grid`](/api/aria/grid/Grid) +- [`GridRow`](/api/aria/grid/GridRow) +- [`GridCell`](/api/aria/grid/GridCell) +- [`GridCellWidget`](/api/aria/grid/GridCellWidget) diff --git a/adev-es/src/content/guide/aria/listbox.en.md b/adev-es/src/content/guide/aria/listbox.en.md index a1863dc1..c4a6569e 100644 --- a/adev-es/src/content/guide/aria/listbox.en.md +++ b/adev-es/src/content/guide/aria/listbox.en.md @@ -72,7 +72,7 @@ Applications sometimes need selectable lists visible directly on the page rather -The `values` model signal provides two-way binding to the selected items. With `selectionMode="explicit"`, users press Space or Enter to select options. For dropdown patterns that combine listbox with combobox and overlay positioning, see the [Select](guide/aria/select) pattern. +The `value` model signal provides two-way binding to the selected items. With `selectionMode="explicit"`, users press Space or Enter to select options. For dropdown patterns that combine listbox with combobox and overlay positioning, see the [Select](guide/aria/select) pattern. ### Horizontal listbox @@ -108,86 +108,93 @@ With `orientation="horizontal"`, left and right arrow keys navigate between opti ### Selection modes -Listbox supports two selection modes that control when items become selected. Choose the mode that matches your interface's interaction pattern. +Listbox supports two selection modes that control when items become selected. - - - +The `'follow'` mode automatically selects the focused item, providing faster interaction when selection changes frequently. The `'explicit'` mode requires Space or Enter to confirm selection, preventing accidental changes while navigating. Dropdown patterns typically use `'follow'` mode for single selection. + +#### Explicit + + + + -The `'follow'` mode automatically selects the focused item, providing faster interaction when selection changes frequently. The `'explicit'` mode requires Space or Enter to confirm selection, preventing accidental changes while navigating. Dropdown patterns typically use `'follow'` mode for single selection. +#### Follow -## APIs + + + + -### Listbox Directive +| Mode | Description | +| ------------ | ------------------------------------------------------------------------------------------------------ | +| `'follow'` | Automatically selects the focused item, providing faster interaction when selection changes frequently | +| `'explicit'` | Requires Space or Enter to confirm selection, preventing accidental changes while navigating | -The `ngListbox` directive creates an accessible list of selectable options. +TIP: Dropdown patterns typically use `'follow'` mode for single selection. -#### Inputs +## Testing -| Property | Type | Default | Description | -| ---------------- | ---------------------------------- | ------------ | -------------------------------------------- | -| `id` | `string` | auto | Unique identifier for the listbox | -| `multi` | `boolean` | `false` | Enables multiple selection | -| `orientation` | `'vertical'` \| `'horizontal'` | `'vertical'` | Layout direction of the list | -| `wrap` | `boolean` | `true` | Whether focus wraps at list edges | -| `selectionMode` | `'follow'` \| `'explicit'` | `'follow'` | How selection is triggered | -| `focusMode` | `'roving'` \| `'activedescendant'` | `'roving'` | Focus management strategy | -| `softDisabled` | `boolean` | `true` | Whether disabled items are focusable | -| `disabled` | `boolean` | `false` | Disables the entire listbox | -| `readonly` | `boolean` | `false` | Makes listbox readonly | -| `typeaheadDelay` | `number` | `500` | Milliseconds before type-ahead search resets | +Angular Aria provides component harnesses for testing listbox components. +Here is an example of how to use the harnesses in a component test: -#### Model +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ListboxHarness} from '@angular/aria/listbox/testing'; +import {MyListboxComponent} from './my-listbox'; // Your component -| Property | Type | Description | -| -------- | ----- | ----------------------------------------- | -| `values` | `V[]` | Two-way bindable array of selected values | +describe('MyListboxComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -#### Signals + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyListboxComponent], + }); -| Property | Type | Description | -| -------- | ------------- | ------------------------------------- | -| `values` | `Signal` | Currently selected values as a signal | + fixture = TestBed.createComponent(MyListboxComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -#### Methods + it('should allow selecting options', async () => { + const listbox = await loader.getHarness(ListboxHarness); -| Method | Parameters | Description | -| -------------------------- | --------------------------------- | ------------------------------------------ | -| `scrollActiveItemIntoView` | `options?: ScrollIntoViewOptions` | Scrolls the active item into view | -| `gotoFirst` | none | Navigates to the first item in the listbox | + // Verify listbox properties + expect(await listbox.isMulti()).toBe(true); -### Option Directive + // Get all options + const options = await listbox.getOptions(); + expect(options.length).toBe(2); -The `ngOption` directive marks an item within a listbox. + // Click an option + await options[0].click(); -#### Inputs + // Verify option is selected + expect(await options[0].isSelected()).toBe(true); -| Property | Type | Default | Description | -| ---------- | --------- | ------- | ------------------------------------------------ | -| `id` | `string` | auto | Unique identifier for the option | -| `value` | `V` | - | The value associated with this option (required) | -| `label` | `string` | - | Optional label for screen readers | -| `disabled` | `boolean` | `false` | Whether this option is disabled | + // Filter options by text + const bananaOption = await listbox.getOptions({text: 'Banana'}); + expect(bananaOption.length).toBe(1); + }); +}); +``` -#### Signals +## API reference -| Property | Type | Description | -| ---------- | ----------------- | ------------------------------- | -| `selected` | `Signal` | Whether this option is selected | -| `active` | `Signal` | Whether this option has focus | +For detailed API documentation, inspect the following API references: + +- [`Listbox`](/api/aria/listbox/Listbox) +- [`Option`](/api/aria/listbox/Option) ### Related patterns Listbox is used by these documented dropdown patterns: -- **[Select](guide/aria/select)** - Single-selection dropdown pattern using readonly combobox + listbox -- **[Multiselect](guide/aria/multiselect)** - Multiple-selection dropdown pattern using readonly combobox + listbox with `multi` -- **[Autocomplete](guide/aria/autocomplete)** - Filterable dropdown pattern using combobox + listbox +- [Select](guide/aria/select) - Single-selection dropdown pattern using readonly combobox + listbox +- [Multiselect](guide/aria/multiselect) - Multiple-selection dropdown pattern using readonly combobox + listbox with `multi` +- [Autocomplete](guide/aria/autocomplete) - Filterable dropdown pattern using combobox + listbox For complete dropdown patterns with trigger, popup, and overlay positioning, see those pattern guides instead of using listbox alone. - - - - - diff --git a/adev-es/src/content/guide/aria/listbox.md b/adev-es/src/content/guide/aria/listbox.md index 74a3162e..e20a9af2 100644 --- a/adev-es/src/content/guide/aria/listbox.md +++ b/adev-es/src/content/guide/aria/listbox.md @@ -6,7 +6,7 @@ -## Visión general +## Visión general {#overview} Una directiva que muestra una lista de opciones para que los usuarios seleccionen, soportando navegación por teclado, selección simple o múltiple, y soporte para lectores de pantalla. @@ -36,7 +36,7 @@ Una directiva que muestra una lista de opciones para que los usuarios seleccione -## Uso +## Uso {#usage} Listbox es una directiva fundamental utilizada por los patrones [Select](guide/aria/select), [Multiselect](guide/aria/multiselect) y [Autocomplete](guide/aria/autocomplete). Para la mayoría de las necesidades de dropdown, usa esos patrones documentados en su lugar. @@ -50,7 +50,7 @@ Evita listbox cuando: - **Se necesitan menús de navegación** - Usa la directiva [Menu](guide/aria/menu) para acciones y comandos -## Características +## Características {#features} El listbox de Angular proporciona una implementación de lista completamente accesible con: @@ -61,9 +61,9 @@ El listbox de Angular proporciona una implementación de lista completamente acc - **Búsqueda por Escritura** - Escribe caracteres para saltar a opciones coincidentes - **Reactividad Basada en Signals** - Gestión de estado reactivo usando signals de Angular -## Ejemplos +## Ejemplos {#examples} -### Listbox básico +### Listbox básico {#basic-listbox} Las aplicaciones a veces necesitan listas seleccionables visibles directamente en la página en lugar de ocultas en un dropdown. Un listbox independiente proporciona navegación por teclado y selección para estas interfaces de lista visibles. @@ -74,7 +74,7 @@ Las aplicaciones a veces necesitan listas seleccionables visibles directamente e El signal del modelo `values` proporciona enlace bidireccional a los elementos seleccionados. Con `selectionMode="explicit"`, los usuarios presionan Espacio o Enter para seleccionar opciones. Para patrones de dropdown que combinan listbox con combobox y posicionamiento de overlay, consulta el patrón [Select](guide/aria/select). -### Listbox horizontal +### Listbox horizontal {#horizontal-listbox} Las listas a veces funcionan mejor horizontalmente, como interfaces tipo toolbar o selecciones estilo pestañas. El atributo `orientation` cambia tanto el diseño como la dirección de navegación por teclado. @@ -106,88 +106,88 @@ Las listas a veces funcionan mejor horizontalmente, como interfaces tipo toolbar Con `orientation="horizontal"`, las teclas de flecha izquierda y derecha navegan entre opciones en lugar de arriba y abajo. El listbox maneja automáticamente idiomas de derecha a izquierda (RTL) invirtiendo la dirección de navegación. -### Modos de selección +### Modos de selección {#selection-modes} Listbox soporta dos modos de selección que controlan cuándo los elementos se seleccionan. Elige el modo que coincida con el patrón de interacción de tu interfaz. - - - +#### Explicit + + + + -El modo `'follow'` selecciona automáticamente el elemento enfocado, proporcionando interacción más rápida cuando la selección cambia frecuentemente. El modo `'explicit'` requiere Espacio o Enter para confirmar la selección, previniendo cambios accidentales mientras se navega. Los patrones de dropdown típicamente usan el modo `'follow'` para selección simple. +#### Follow -## APIs + + + + -### Directiva Listbox +El modo `'follow'` selecciona automáticamente el elemento enfocado, proporcionando interacción más rápida cuando la selección cambia frecuentemente. El modo `'explicit'` requiere Espacio o Enter para confirmar la selección, previniendo cambios accidentales mientras se navega. Los patrones de dropdown típicamente usan el modo `'follow'` para selección simple. -La directiva `ngListbox` crea una lista accesible de opciones seleccionables. +## Testing -#### Inputs +Angular Aria proporciona harnesses de componentes para probar componentes listbox. +Aquí hay un ejemplo de cómo usar los harnesses en una prueba de componente: -| Propiedad | Tipo | Por defecto | Descripción | -| ---------------- | ---------------------------------- | ------------ | ----------------------------------------------------- | -| `id` | `string` | auto | Identificador único para el listbox | -| `multi` | `boolean` | `false` | Habilita selección múltiple | -| `orientation` | `'vertical'` \| `'horizontal'` | `'vertical'` | Dirección de diseño de la lista | -| `wrap` | `boolean` | `true` | Si el foco se envuelve en los bordes de la lista | -| `selectionMode` | `'follow'` \| `'explicit'` | `'follow'` | Cómo se activa la selección | -| `focusMode` | `'roving'` \| `'activedescendant'` | `'roving'` | Estrategia de gestión de foco | -| `softDisabled` | `boolean` | `true` | Si los elementos deshabilitados son enfocables | -| `disabled` | `boolean` | `false` | Deshabilita todo el listbox | -| `readonly` | `boolean` | `false` | Hace el listbox de solo lectura | -| `typeaheadDelay` | `number` | `500` | Milisegundos antes de que se reinicie la búsqueda | +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ListboxHarness} from '@angular/aria/listbox/testing'; +import {MyListboxComponent} from './my-listbox'; // Tu componente -#### Model +describe('MyListboxComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -| Propiedad | Tipo | Descripción | -| --------- | ----- | ---------------------------------------------------- | -| `values` | `V[]` | Array enlazable bidireccionalmente de valores seleccionados | + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyListboxComponent], + }); -#### Signals + fixture = TestBed.createComponent(MyListboxComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -| Propiedad | Tipo | Descripción | -| --------- | ------------- | ------------------------------------------- | -| `values` | `Signal` | Valores seleccionados actualmente como signal | + it('should allow selecting options', async () => { + const listbox = await loader.getHarness(ListboxHarness); -#### Métodos + // Verifica las propiedades del listbox + expect(await listbox.isMulti()).toBe(true); -| Método | Parámetros | Descripción | -| -------------------------- | --------------------------------- | ---------------------------------------------- | -| `scrollActiveItemIntoView` | `options?: ScrollIntoViewOptions` | Desplaza el elemento activo a la vista | -| `gotoFirst` | none | Navega al primer elemento del listbox | + // Obtiene todas las opciones + const options = await listbox.getOptions(); + expect(options.length).toBe(2); -### Directiva Option + // Hace clic en una opción + await options[0].click(); -La directiva `ngOption` marca un elemento dentro de un listbox. + // Verifica que la opción está seleccionada + expect(await options[0].isSelected()).toBe(true); -#### Inputs + // Filtra opciones por texto + const bananaOption = await listbox.getOptions({text: 'Banana'}); + expect(bananaOption.length).toBe(1); + }); +}); +``` -| Propiedad | Tipo | Por defecto | Descripción | -| ---------- | --------- | ----------- | ------------------------------------------------------- | -| `id` | `string` | auto | Identificador único para la opción | -| `value` | `V` | - | El valor asociado con esta opción (requerido) | -| `label` | `string` | - | Etiqueta opcional para lectores de pantalla | -| `disabled` | `boolean` | `false` | Si esta opción está deshabilitada | +## API reference -#### Signals +Para documentación de API detallada, inspecciona las siguientes referencias de API: -| Propiedad | Tipo | Descripción | -| ---------- | ----------------- | ------------------------------------- | -| `selected` | `Signal` | Si esta opción está seleccionada | -| `active` | `Signal` | Si esta opción tiene el foco | +- [`Listbox`](/api/aria/listbox/Listbox) +- [`Option`](/api/aria/listbox/Option) -### Patrones relacionados +### Patrones relacionados {#related-patterns} Listbox es usado por estos patrones de dropdown documentados: -- **[Select](guide/aria/select)** - Patrón de dropdown de selección simple usando combobox de solo lectura + listbox -- **[Multiselect](guide/aria/multiselect)** - Patrón de dropdown de selección múltiple usando combobox de solo lectura + listbox con `multi` -- **[Autocomplete](guide/aria/autocomplete)** - Patrón de dropdown filtrable usando combobox + listbox +- [Select](guide/aria/select) - Patrón de dropdown de selección única usando combobox readonly + listbox +- [Multiselect](guide/aria/multiselect) - Patrón de dropdown de selección múltiple usando combobox readonly + listbox con `multi` +- [Autocomplete](guide/aria/autocomplete) - Patrón de dropdown filtrable usando combobox + listbox -Para patrones completos de dropdown con trigger, popup y posicionamiento de overlay, consulta esas guías de patrones en lugar de usar listbox solo. - - - - - +Para patrones de dropdown completos con trigger, popup y posicionamiento de overlay, consulta esas guías de patrones en lugar de usar listbox solo. diff --git a/adev-es/src/content/guide/aria/menu.en.md b/adev-es/src/content/guide/aria/menu.en.md index 444325cf..dc2ebe46 100644 --- a/adev-es/src/content/guide/aria/menu.en.md +++ b/adev-es/src/content/guide/aria/menu.en.md @@ -174,85 +174,82 @@ Disable specific menu items using the `disabled` input. Control focus behavior w When `[softDisabled]="true"`, disabled items can receive focus but cannot be activated. When `[softDisabled]="false"`, disabled items are skipped during keyboard navigation. -## APIs - -### Menu - -The container directive for menu items. - -#### Inputs - -| Property | Type | Default | Description | -| -------------- | --------- | ------- | ------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Disables all items in the menu | -| `wrap` | `boolean` | `true` | Whether keyboard navigation wraps at edges | -| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable but not interactive | - -#### Methods - -| Method | Parameters | Description | -| ---------------- | ---------- | ---------------------------------- | -| `close` | none | Closes the menu | -| `focusFirstItem` | none | Moves focus to the first menu item | - -### MenuBar - -A horizontal container for multiple menus. - -#### Inputs - -| Property | Type | Default | Description | -| -------------- | --------- | ------- | ------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Disables the entire menubar | -| `wrap` | `boolean` | `true` | Whether keyboard navigation wraps at edges | -| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable but not interactive | - -### MenuItem - -An individual item within a menu. - -#### Inputs - -| Property | Type | Default | Description | -| ------------ | --------- | ------- | ---------------------------------------------------- | -| `value` | `any` | — | **Required.** Value for this item | -| `disabled` | `boolean` | `false` | Disables this menu item | -| `submenu` | `Menu` | — | Reference to a submenu | -| `searchTerm` | `string` | `''` | Search term for typeahead (supports two-way binding) | - -#### Signals - -| Property | Type | Description | -| ---------- | ----------------- | ------------------------------------------ | -| `active` | `Signal` | Whether the item currently has focus | -| `expanded` | `Signal` | Whether the submenu is expanded | -| `hasPopup` | `Signal` | Whether the item has an associated submenu | - -NOTE: MenuItem does not expose public methods. Use the `submenu` input to associate submenus with menu items. - -### MenuTrigger - -A button or element that opens a menu. - -#### Inputs - -| Property | Type | Default | Description | -| -------------- | --------- | ------- | ------------------------------------------ | -| `menu` | `Menu` | — | **Required.** The menu to trigger | -| `disabled` | `boolean` | `false` | Disables the trigger | -| `softDisabled` | `boolean` | `true` | When `true`, disabled trigger is focusable | - -#### Signals - -| Property | Type | Description | -| ---------- | ----------------- | ------------------------------------------ | -| `expanded` | `Signal` | Whether the menu is currently open | -| `hasPopup` | `Signal` | Whether the trigger has an associated menu | - -#### Methods - -| Method | Parameters | Description | -| -------- | ---------- | ---------------------------- | -| `open` | none | Opens the menu | -| `close` | none | Closes the menu | -| `toggle` | none | Toggles the menu open/closed | +## Testing + +Angular Aria provides component harnesses for testing menu components. +Here is an example of how to use the harnesses in a component test: + +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {MenuHarness, MenuItemHarness} from '@angular/aria/menu/testing'; +import {MyMenuComponent} from './my-menu'; // Your component + +describe('MyMenuComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyMenuComponent], + }); + + fixture = TestBed.createComponent(MyMenuComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should open menu and click item', async () => { + // Load the menu harness by its trigger text + const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); + + // Verify initial state + expect(await menu.isOpen()).toBe(false); + + // Open the menu + await menu.open(); + expect(await menu.isOpen()).toBe(true); + + // Get items + const items = await menu.getItems(); + expect(items.length).toBe(3); + expect(await items[0].getText()).toBe('Item 1'); + + // Click first item + await items[0].click(); + + // Menu should close after selection (depending on your implementation) + expect(await menu.isOpen()).toBe(false); + }); + + it('should interact with submenus', async () => { + const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); + await menu.open(); + + // Get the item that triggers a submenu + const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'})); + expect(await subItem.hasSubmenu()).toBe(true); + + // Open submenu + await subItem.click(); + const submenu = await subItem.getSubmenu(); + expect(submenu).toBeTruthy(); + expect(await submenu!.isOpen()).toBe(true); + + // Interact with submenu items + const subItems = await submenu!.getItems(); + expect(subItems.length).toBe(1); + }); +}); +``` + +## API reference + +For detailed API documentation, inspect the following API references: + +- [`Menu`](/api/aria/menu/Menu) +- [`MenuBar`](/api/aria/menu/MenuBar) +- [`MenuItem`](/api/aria/menu/MenuItem) +- [`MenuTrigger`](/api/aria/menu/MenuTrigger) +- [`MenuContent`](/api/aria/menu/MenuContent) diff --git a/adev-es/src/content/guide/aria/menu.md b/adev-es/src/content/guide/aria/menu.md index 83a338e6..d8561c4e 100644 --- a/adev-es/src/content/guide/aria/menu.md +++ b/adev-es/src/content/guide/aria/menu.md @@ -6,7 +6,7 @@ -## Visión general +## Visión general {#overview} Un menú ofrece una lista de acciones u opciones a los usuarios, apareciendo típicamente en respuesta a un clic en un botón o clic derecho. Los menús soportan navegación por teclado con teclas de flecha, submenús, casillas de verificación, botones de radio y elementos deshabilitados. @@ -36,7 +36,7 @@ Un menú ofrece una lista de acciones u opciones a los usuarios, apareciendo tí -## Uso +## Uso {#usage} Los menús funcionan bien para presentar listas de acciones o comandos que los usuarios pueden elegir. @@ -55,7 +55,7 @@ Los menús funcionan bien para presentar listas de acciones o comandos que los u - Cambiar entre paneles de contenido (usa [Tabs](guide/aria/tabs)) - Mostrar contenido colapsable (usa [Accordion](guide/aria/accordion)) -## Características +## Características {#features} - **Navegación por teclado** - Teclas de flecha, Home/End y búsqueda por caracteres para navegación eficiente - **Submenús** - Soporte de menús anidados con posicionamiento automático @@ -65,9 +65,9 @@ Los menús funcionan bien para presentar listas de acciones o comandos que los u - **Comportamiento de auto-cierre** - Cierre configurable al seleccionar - **Soporte RTL** - Navegación para idiomas de derecha a izquierda -## Ejemplos +## Ejemplos {#examples} -### Menú con trigger +### Menú con trigger {#menu-with-trigger} Crea un menú desplegable emparejando un botón trigger con un menú. El trigger abre y cierra el menú. @@ -99,7 +99,7 @@ Crea un menú desplegable emparejando un botón trigger con un menú. El trigger El menú se cierra automáticamente cuando un usuario selecciona un elemento o presiona Escape. -### Menú contextual +### Menú contextual {#context-menu} Los menús contextuales aparecen en la posición del cursor cuando los usuarios hacen clic derecho en un elemento. @@ -110,7 +110,7 @@ Los menús contextuales aparecen en la posición del cursor cuando los usuarios Posiciona el menú usando las coordenadas del evento `contextmenu`. -### Menú independiente +### Menú independiente {#standalone-menu} Un menú independiente no requiere un trigger y permanece visible en la interfaz. @@ -142,7 +142,7 @@ Un menú independiente no requiere un trigger y permanece visible en la interfaz Los menús independientes funcionan bien para listas de acciones o navegación siempre visibles. -### Elementos de menú deshabilitados +### Elementos de menú deshabilitados {#disabled-menu-items} Deshabilita elementos específicos del menú usando el input `disabled`. Controla el comportamiento del foco con `softDisabled`. @@ -174,85 +174,82 @@ Deshabilita elementos específicos del menú usando el input `disabled`. Control Cuando `[softDisabled]="true"`, los elementos deshabilitados pueden recibir foco pero no pueden ser activados. Cuando `[softDisabled]="false"`, los elementos deshabilitados se omiten durante la navegación por teclado. -## APIs - -### Menu - -La directiva contenedor para elementos de menú. - -#### Inputs - -| Propiedad | Tipo | Por defecto | Descripción | -| -------------- | --------- | ----------- | ---------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Deshabilita todos los elementos del menú | -| `wrap` | `boolean` | `true` | Si la navegación por teclado se envuelve en los bordes | -| `softDisabled` | `boolean` | `true` | Cuando es `true`, los elementos deshabilitados son enfocables pero no interactivos | - -#### Métodos - -| Método | Parámetros | Descripción | -| ---------------- | ---------- | -------------------------------------- | -| `close` | none | Cierra el menú | -| `focusFirstItem` | none | Mueve el foco al primer elemento del menú | - -### MenuBar - -Un contenedor horizontal para múltiples menús. - -#### Inputs - -| Propiedad | Tipo | Por defecto | Descripción | -| -------------- | --------- | ----------- | ---------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Deshabilita todo el menubar | -| `wrap` | `boolean` | `true` | Si la navegación por teclado se envuelve en los bordes | -| `softDisabled` | `boolean` | `true` | Cuando es `true`, los elementos deshabilitados son enfocables pero no interactivos | - -### MenuItem - -Un elemento individual dentro de un menú. - -#### Inputs - -| Propiedad | Tipo | Por defecto | Descripción | -| ------------ | --------- | ----------- | ----------------------------------------------------- | -| `value` | `any` | — | **Requerido.** Valor para este elemento | -| `disabled` | `boolean` | `false` | Deshabilita este elemento del menú | -| `submenu` | `Menu` | — | Referencia a un submenú | -| `searchTerm` | `string` | `''` | Término de búsqueda para typeahead (soporta enlace bidireccional) | - -#### Signals - -| Propiedad | Tipo | Descripción | -| ---------- | ----------------- | --------------------------------------------- | -| `active` | `Signal` | Si el elemento tiene el foco actualmente | -| `expanded` | `Signal` | Si el submenú está expandido | -| `hasPopup` | `Signal` | Si el elemento tiene un submenú asociado | - -NOTA: MenuItem no expone métodos públicos. Usa el input `submenu` para asociar submenús con elementos de menú. - -### MenuTrigger - -Un botón o elemento que abre un menú. - -#### Inputs - -| Propiedad | Tipo | Por defecto | Descripción | -| -------------- | --------- | ----------- | --------------------------------------------- | -| `menu` | `Menu` | — | **Requerido.** El menú a activar | -| `disabled` | `boolean` | `false` | Deshabilita el trigger | -| `softDisabled` | `boolean` | `true` | Cuando es `true`, el trigger deshabilitado es enfocable | - -#### Signals - -| Propiedad | Tipo | Descripción | -| ---------- | ----------------- | --------------------------------------------- | -| `expanded` | `Signal` | Si el menú está abierto actualmente | -| `hasPopup` | `Signal` | Si el trigger tiene un menú asociado | - -#### Métodos - -| Método | Parámetros | Descripción | -| -------- | ---------- | --------------------------------- | -| `open` | none | Abre el menú | -| `close` | none | Cierra el menú | -| `toggle` | none | Alterna el menú abierto/cerrado | +## Testing + +Angular Aria proporciona harnesses de componentes para probar componentes menu. +Aquí hay un ejemplo de cómo usar los harnesses en una prueba de componente: + +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {MenuHarness, MenuItemHarness} from '@angular/aria/menu/testing'; +import {MyMenuComponent} from './my-menu'; // Tu componente + +describe('MyMenuComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyMenuComponent], + }); + + fixture = TestBed.createComponent(MyMenuComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should open menu and click item', async () => { + // Carga el harness del menú por el texto de su trigger + const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); + + // Verifica el estado inicial + expect(await menu.isOpen()).toBe(false); + + // Abre el menú + await menu.open(); + expect(await menu.isOpen()).toBe(true); + + // Obtiene los elementos + const items = await menu.getItems(); + expect(items.length).toBe(3); + expect(await items[0].getText()).toBe('Item 1'); + + // Hace clic en el primer elemento + await items[0].click(); + + // El menú debería cerrarse después de la selección (dependiendo de tu implementación) + expect(await menu.isOpen()).toBe(false); + }); + + it('should interact with submenus', async () => { + const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'})); + await menu.open(); + + // Obtiene el elemento que activa un submenú + const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'})); + expect(await subItem.hasSubmenu()).toBe(true); + + // Abre el submenú + await subItem.click(); + const submenu = await subItem.getSubmenu(); + expect(submenu).toBeTruthy(); + expect(await submenu!.isOpen()).toBe(true); + + // Interactúa con los elementos del submenú + const subItems = await submenu!.getItems(); + expect(subItems.length).toBe(1); + }); +}); +``` + +## API reference + +Para documentación de API detallada, inspecciona las siguientes referencias de API: + +- [`Menu`](/api/aria/menu/Menu) +- [`MenuBar`](/api/aria/menu/MenuBar) +- [`MenuItem`](/api/aria/menu/MenuItem) +- [`MenuTrigger`](/api/aria/menu/MenuTrigger) +- [`MenuContent`](/api/aria/menu/MenuContent) diff --git a/adev-es/src/content/guide/aria/menubar.en.md b/adev-es/src/content/guide/aria/menubar.en.md index 1abedb0c..80f82211 100644 --- a/adev-es/src/content/guide/aria/menubar.en.md +++ b/adev-es/src/content/guide/aria/menubar.en.md @@ -8,7 +8,7 @@ ## Overview -The manubar is a horizontal navigation bar that provides persistent access to application menus. Menubars organize commands into logical categories like File, Edit, and View, helping users discover and execute application features through keyboard or mouse interaction. +The menubar is a horizontal navigation bar that provides persistent access to application menus. Menubars organize commands into logical categories like File, Edit, and View, helping users discover and execute application features through keyboard or mouse interaction. @@ -164,35 +164,61 @@ Menubars automatically adapt to right-to-left (RTL) languages. Arrow key navigat The `dir="rtl"` attribute enables RTL mode. Left arrow moves right, Right arrow moves left, maintaining natural navigation for RTL language users. -## APIs +## Testing -The menubar pattern uses directives from Angular's Aria library. See the [Menu guide](guide/aria/menu) for complete API documentation. +Angular Aria provides component harnesses for testing menubar components. +Here is an example of how to use the harnesses in a component test: -### MenuBar +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {MenuHarness} from '@angular/aria/menu/testing'; +import {MyMenubarComponent} from './my-menubar'; // Your component -The horizontal container for top-level menu items. +describe('MyMenubarComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -#### Inputs + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyMenubarComponent], + }); -| Property | Type | Default | Description | -| -------------- | --------- | ------- | ------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Disables the entire menubar | -| `wrap` | `boolean` | `true` | Whether keyboard navigation wraps from last to first item | -| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable but not interactive | + fixture = TestBed.createComponent(MyMenubarComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -See the [Menu API documentation](guide/aria/menu#apis) for complete details on all available inputs and signals. + it('should interact with menubar items', async () => { + // Load the menubar harness (which is a MenuHarness with selector '[ngMenuBar]') + const menubar = await loader.getHarness(MenuHarness.with({selector: '[ngMenuBar]'})); -### MenuItem + // Menubars are persistent and always "open" + expect(await menubar.isOpen()).toBe(true); + expect(await menubar.isMenuBar()).toBe(true); -Individual items within the menubar. Same API as Menu - see [MenuItem](guide/aria/menu#menuitem). + // Get top-level items + const items = await menubar.getItems(); + expect(items.length).toBe(2); + expect(await items[0].getText()).toBe('File'); + expect(await items[1].getText()).toBe('Edit'); -**Menubar-specific behavior:** + // Click an item to open its dropdown menu + await items[0].click(); -- Left/Right arrows navigate between menubar items (vs Up/Down in vertical menus) -- First keyboard interaction or click enables hover-to-open for submenus -- Enter or Down arrow opens the submenu and focuses the first item -- `aria-haspopup="menu"` indicates items with submenus + const fileMenu = await items[0].getSubmenu(); + expect(fileMenu).toBeTruthy(); + expect(await fileMenu!.isOpen()).toBe(true); + }); +}); +``` -### MenuTrigger +## API reference -Not typically used in menubars - MenuItem handles trigger behavior directly when it has an associated submenu. See [MenuTrigger](guide/aria/menu#menutrigger) for standalone menu trigger patterns. +For detailed API documentation, inspect the following API references: + +- [`MenuBar`](/api/aria/menu/MenuBar) +- [`MenuItem`](/api/aria/menu/MenuItem) +- [`MenuTrigger`](/api/aria/menu/MenuTrigger) +- [`Menu`](/api/aria/menu/Menu) diff --git a/adev-es/src/content/guide/aria/menubar.md b/adev-es/src/content/guide/aria/menubar.md index 7f094bf1..0ecd5abc 100644 --- a/adev-es/src/content/guide/aria/menubar.md +++ b/adev-es/src/content/guide/aria/menubar.md @@ -6,7 +6,7 @@ -## Visión general +## Visión general {#overview} El menubar es una barra de navegación horizontal que proporciona acceso persistente a los menús de la aplicación. Los menubars organizan comandos en categorías lógicas como File, Edit y View, ayudando a los usuarios a descubrir y ejecutar características de la aplicación a través de interacción por teclado o mouse. @@ -36,7 +36,7 @@ El menubar es una barra de navegación horizontal que proporciona acceso persist -## Uso +## Uso {#usage} Los menubars funcionan bien para organizar comandos de aplicación en navegación persistente y descubrible. @@ -56,7 +56,7 @@ Los menubars funcionan bien para organizar comandos de aplicación en navegació - Interfaces móviles donde el espacio horizontal es limitado - La navegación pertenece a un patrón de navegación de sidebar o header -## Características +## Características {#features} - **Navegación horizontal** - Las teclas de flecha Izquierda/Derecha se mueven entre categorías de nivel superior - **Visibilidad persistente** - Siempre visible, no modal o descartable @@ -66,9 +66,9 @@ Los menubars funcionan bien para organizar comandos de aplicación en navegació - **Estados deshabilitados** - Deshabilitar todo el menubar o elementos individuales - **Soporte RTL** - Navegación automática para idiomas de derecha a izquierda -## Ejemplos +## Ejemplos {#examples} -### Menubar básico +### Menubar básico {#basic-menubar} Un menubar proporciona acceso persistente a comandos de aplicación organizados en categorías de nivel superior. Los usuarios navegan entre categorías con flechas Izquierda/Derecha y abren menús con Enter o flecha Abajo. @@ -100,7 +100,7 @@ Un menubar proporciona acceso persistente a comandos de aplicación organizados Presiona la flecha Derecha para moverte entre File, Edit y View. Presiona Enter o flecha Abajo para abrir un menú y navegar por los elementos del submenú con flechas Arriba/Abajo. -### Elementos de menubar deshabilitados +### Elementos de menubar deshabilitados {#disabled-menubar-items} Deshabilita elementos de menú específicos o todo el menubar para prevenir interacción. Controla si los elementos deshabilitados pueden recibir foco de teclado con el input `softDisabled`. @@ -132,7 +132,7 @@ Deshabilita elementos de menú específicos o todo el menubar para prevenir inte Cuando `[softDisabled]="true"` en el menubar, los elementos deshabilitados pueden recibir foco pero no pueden ser activados. Cuando `[softDisabled]="false"`, los elementos deshabilitados se omiten durante la navegación por teclado. -### Soporte RTL +### Soporte RTL {#rtl-support} Los menubars se adaptan automáticamente a idiomas de derecha a izquierda (RTL). La navegación por teclas de flecha invierte la dirección y los submenús se posicionan en el lado izquierdo. @@ -164,35 +164,61 @@ Los menubars se adaptan automáticamente a idiomas de derecha a izquierda (RTL). El atributo `dir="rtl"` habilita el modo RTL. La flecha Izquierda se mueve a la derecha, la flecha Derecha se mueve a la izquierda, manteniendo navegación natural para usuarios de idiomas RTL. -## APIs +## Testing -El patrón de menubar usa directivas de la biblioteca Aria de Angular. Consulta la [guía de Menu](guide/aria/menu) para documentación completa del API. +Angular Aria proporciona harnesses de componentes para probar componentes menubar. +Aquí hay un ejemplo de cómo usar los harnesses en una prueba de componente: -### MenuBar +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {MenuHarness} from '@angular/aria/menu/testing'; +import {MyMenubarComponent} from './my-menubar'; // Tu componente -El contenedor horizontal para elementos de menú de nivel superior. +describe('MyMenubarComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -#### Inputs + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyMenubarComponent], + }); -| Propiedad | Tipo | Por defecto | Descripción | -| -------------- | --------- | ----------- | ---------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Deshabilita todo el menubar | -| `wrap` | `boolean` | `true` | Si la navegación por teclado se envuelve del último al primer elemento | -| `softDisabled` | `boolean` | `true` | Cuando es `true`, los elementos deshabilitados son enfocables pero no interactivos | + fixture = TestBed.createComponent(MyMenubarComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -Consulta la [documentación del API de Menu](guide/aria/menu#apis) para detalles completos sobre todos los inputs y signals disponibles. + it('should interact with menubar items', async () => { + // Carga el harness del menubar (que es un MenuHarness con selector '[ngMenuBar]') + const menubar = await loader.getHarness(MenuHarness.with({selector: '[ngMenuBar]'})); -### MenuItem + // Los menubars son persistentes y siempre están "abiertos" + expect(await menubar.isOpen()).toBe(true); + expect(await menubar.isMenuBar()).toBe(true); -Elementos individuales dentro del menubar. El mismo API que Menu - consulta [MenuItem](guide/aria/menu#menuitem). + // Obtiene los elementos de nivel superior + const items = await menubar.getItems(); + expect(items.length).toBe(2); + expect(await items[0].getText()).toBe('File'); + expect(await items[1].getText()).toBe('Edit'); -**Comportamiento específico del menubar:** + // Hace clic en un elemento para abrir su menú desplegable + await items[0].click(); -- Las flechas Izquierda/Derecha navegan entre elementos del menubar (vs Arriba/Abajo en menús verticales) -- La primera interacción por teclado o clic habilita hover para abrir para submenús -- Enter o flecha Abajo abre el submenú y enfoca el primer elemento -- `aria-haspopup="menu"` indica elementos con submenús + const fileMenu = await items[0].getSubmenu(); + expect(fileMenu).toBeTruthy(); + expect(await fileMenu!.isOpen()).toBe(true); + }); +}); +``` -### MenuTrigger +## API reference -No se usa típicamente en menubars - MenuItem maneja el comportamiento de trigger directamente cuando tiene un submenú asociado. Consulta [MenuTrigger](guide/aria/menu#menutrigger) para patrones de trigger de menú independiente. +Para documentación de API detallada, inspecciona las siguientes referencias de API: + +- [`MenuBar`](/api/aria/menu/MenuBar) +- [`MenuItem`](/api/aria/menu/MenuItem) +- [`MenuTrigger`](/api/aria/menu/MenuTrigger) +- [`Menu`](/api/aria/menu/Menu) diff --git a/adev-es/src/content/guide/aria/multiselect.en.md b/adev-es/src/content/guide/aria/multiselect.en.md index dc27f4a6..c430db64 100644 --- a/adev-es/src/content/guide/aria/multiselect.en.md +++ b/adev-es/src/content/guide/aria/multiselect.en.md @@ -3,7 +3,7 @@ ## Overview -A pattern that combines readonly combobox with multi-enabled listbox to create multiple-selection dropdowns with keyboard navigation and screen reader support. +The multiselect pattern combines a read-only combobox trigger with a multi-select listbox popup to create highly accessible multiple-selection dropdowns with keyboard navigation and screen reader support. @@ -157,45 +157,70 @@ Forms sometimes need to limit the number of selections or validate user choices. -This example limits selections to three items. When the limit is reached, unselected options become disabled, preventing additional selections. A message informs users about the constraint. +This example limits selections to two items. When the limit is reached, unselected options are disabled to prevent further selections, and the combobox display updates to reflect the choices. -## APIs +## Testing -The multiselect pattern uses the following directives from Angular's Aria library. See the full API documentation in the linked guides. +The multiselect pattern can be tested using a combination of `ComboboxHarness` and `ListboxHarness` from `@angular/aria/combobox/testing` and `@angular/aria/listbox/testing`. +Here is an example of how to use the harnesses to test a multiselect component: -### Combobox Directives +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ComboboxHarness} from '@angular/aria/combobox/testing'; +import {ListboxHarness} from '@angular/aria/listbox/testing'; +import {MyMultiselectComponent} from './my-multiselect'; // Your component -The multiselect pattern uses `ngCombobox` with the `readonly` attribute to prevent text input while preserving keyboard navigation. +describe('MyMultiselectComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -#### Inputs + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyMultiselectComponent], + }); -| Property | Type | Default | Description | -| ---------- | --------- | ------- | ----------------------------------------- | -| `readonly` | `boolean` | `false` | Set to `true` to create dropdown behavior | -| `disabled` | `boolean` | `false` | Disables the entire multiselect | + fixture = TestBed.createComponent(MyMultiselectComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -See the [Combobox API documentation](guide/aria/combobox#apis) for complete details on all available inputs and signals. + it('should allow selecting multiple options', async () => { + const select = await loader.getHarness(ComboboxHarness); -### Listbox Directives + // Open the dropdown + await select.open(); -The multiselect pattern uses `ngListbox` with the `multi` attribute for multiple selection and `ngOption` for each selectable item. + // Get the listbox harness from the popup + const listbox = await select.getPopupWidget(ListboxHarness); + expect(await listbox.isMulti()).toBe(true); -#### Inputs + const options = await listbox.getOptions(); -| Property | Type | Default | Description | -| -------- | --------- | ------- | ------------------------------------------ | -| `multi` | `boolean` | `false` | Set to `true` to enable multiple selection | + // Select first and second options + await options[0].click(); + await options[1].click(); -#### Model + // Verify both options are selected + expect(await options[0].isSelected()).toBe(true); + expect(await options[1].isSelected()).toBe(true); -| Property | Type | Description | -| -------- | ------- | ----------------------------------------- | -| `values` | `any[]` | Two-way bindable array of selected values | + // Close the dropdown + await select.close(); -When `multi` is true, users can select multiple options using Space to toggle selection. The popup remains open after selection, allowing additional choices. + // Verify value is updated (e.g., comma separated list or count) + expect(await (await select.host()).text()).toContain('Option 1, Option 2'); + }); +}); +``` -See the [Listbox API documentation](guide/aria/listbox#apis) for complete details on listbox configuration, selection modes, and option properties. +## API reference -### Positioning +For detailed API documentation, inspect the following API references: -The multiselect pattern integrates with [CDK Overlay](api/cdk/overlay/CdkConnectedOverlay) for smart positioning. Use `cdkConnectedOverlay` to handle viewport edges and scrolling automatically. +- [`Combobox`](/api/aria/combobox/Combobox) +- [`ComboboxPopup`](/api/aria/combobox/ComboboxPopup) +- [`ComboboxWidget`](/api/aria/combobox/ComboboxWidget) +- [`Listbox`](/api/aria/listbox/Listbox) +- [`Option`](/api/aria/listbox/Option) diff --git a/adev-es/src/content/guide/aria/multiselect.md b/adev-es/src/content/guide/aria/multiselect.md index f3d6761b..613543c4 100644 --- a/adev-es/src/content/guide/aria/multiselect.md +++ b/adev-es/src/content/guide/aria/multiselect.md @@ -1,7 +1,7 @@ -## Visión general +## Visión general {#overview} Un patrón que combina combobox de solo lectura con listbox habilitado para múltiple selección para crear dropdowns de selección múltiple con navegación por teclado y soporte para lectores de pantalla. @@ -31,7 +31,7 @@ Un patrón que combina combobox de solo lectura con listbox habilitado para múl -## Uso +## Uso {#usage} El patrón de multiselect funciona mejor cuando los usuarios necesitan elegir múltiples elementos relacionados de un conjunto familiar de opciones. @@ -50,7 +50,7 @@ Evita este patrón cuando: - **La mayoría o todas las opciones serán seleccionadas** - Un patrón de checklist proporciona mejor visibilidad - **Las opciones son opciones binarias independientes** - Las casillas individuales comunican las opciones más claramente -## Características +## Características {#features} El patrón de multiselect combina directivas [Combobox](guide/aria/combobox) y [Listbox](guide/aria/listbox) para proporcionar un dropdown completamente accesible con: @@ -61,9 +61,9 @@ El patrón de multiselect combina directivas [Combobox](guide/aria/combobox) y [ - **Posicionamiento Inteligente** - CDK Overlay maneja bordes de viewport y desplazamiento - **Selección Persistente** - Las opciones seleccionadas permanecen visibles con marcas de verificación después de la selección -## Ejemplos +## Ejemplos {#examples} -### Multiselect básico +### Multiselect básico {#basic-multiselect} Los usuarios necesitan seleccionar múltiples elementos de una lista de opciones. Un combobox de solo lectura emparejado con un listbox habilitado para multi proporciona funcionalidad de multiselección familiar con soporte de accesibilidad completo. @@ -95,7 +95,7 @@ Los usuarios necesitan seleccionar múltiples elementos de una lista de opciones El atributo `multi` en `ngListbox` habilita selección múltiple. Presiona Espacio para alternar opciones, y el popup permanece abierto para selecciones adicionales. La visualización muestra el primer elemento seleccionado más un conteo de selecciones restantes. -### Multiselect con visualización personalizada +### Multiselect con visualización personalizada {#multiselect-with-custom-display} Las opciones a menudo necesitan indicadores visuales como iconos o colores para ayudar a los usuarios a identificar opciones. Las plantillas personalizadas dentro de las opciones permiten formato enriquecido mientras el valor de visualización muestra un resumen compacto. @@ -127,7 +127,7 @@ Las opciones a menudo necesitan indicadores visuales como iconos o colores para Cada opción muestra un icono junto a su etiqueta. El valor de visualización se actualiza para mostrar el icono y texto de la primera selección, seguido de un conteo de selecciones adicionales. Las opciones seleccionadas muestran una marca de verificación para retroalimentación visual clara. -### Selección controlada +### Selección controlada {#controlled-selection} Los formularios a veces necesitan limitar el número de selecciones o validar opciones de usuario. El control programático sobre la selección habilita estas restricciones mientras mantiene la accesibilidad. @@ -159,43 +159,68 @@ Los formularios a veces necesitan limitar el número de selecciones o validar op Este ejemplo limita las selecciones a tres elementos. Cuando se alcanza el límite, las opciones no seleccionadas se deshabilitan, previniendo selecciones adicionales. Un mensaje informa a los usuarios sobre la restricción. -## APIs +## Testing -El patrón de multiselect usa las siguientes directivas de la biblioteca Aria de Angular. Consulta la documentación completa del API en las guías enlazadas. +El patrón multiselect puede probarse usando una combinación de `ComboboxHarness` y `ListboxHarness` de `@angular/aria/combobox/testing` y `@angular/aria/listbox/testing`. +Aquí hay un ejemplo de cómo usar los harnesses para probar un componente multiselect: -### Directivas Combobox +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ComboboxHarness} from '@angular/aria/combobox/testing'; +import {ListboxHarness} from '@angular/aria/listbox/testing'; +import {MyMultiselectComponent} from './my-multiselect'; // Tu componente -El patrón de multiselect usa `ngCombobox` con el atributo `readonly` para prevenir entrada de texto mientras preserva la navegación por teclado. +describe('MyMultiselectComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -#### Inputs + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyMultiselectComponent], + }); -| Propiedad | Tipo | Por defecto | Descripción | -| ---------- | --------- | ----------- | ------------------------------------------------ | -| `readonly` | `boolean` | `false` | Establece a `true` para crear comportamiento de dropdown | -| `disabled` | `boolean` | `false` | Deshabilita todo el multiselect | + fixture = TestBed.createComponent(MyMultiselectComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -Consulta la [documentación del API de Combobox](guide/aria/combobox#apis) para detalles completos sobre todos los inputs y signals disponibles. + it('should allow selecting multiple options', async () => { + const select = await loader.getHarness(ComboboxHarness); -### Directivas Listbox + // Abre el dropdown + await select.open(); -El patrón de multiselect usa `ngListbox` con el atributo `multi` para selección múltiple y `ngOption` para cada elemento seleccionable. + // Obtiene el harness del listbox desde el popup + const listbox = await select.getPopupWidget(ListboxHarness); + expect(await listbox.isMulti()).toBe(true); -#### Inputs + const options = await listbox.getOptions(); -| Propiedad | Tipo | Por defecto | Descripción | -| --------- | --------- | ----------- | --------------------------------------------- | -| `multi` | `boolean` | `false` | Establece a `true` para habilitar selección múltiple | + // Selecciona la primera y segunda opción + await options[0].click(); + await options[1].click(); -#### Model + // Verifica que ambas opciones estén seleccionadas + expect(await options[0].isSelected()).toBe(true); + expect(await options[1].isSelected()).toBe(true); -| Propiedad | Tipo | Descripción | -| --------- | ------- | ---------------------------------------------------- | -| `values` | `any[]` | Array enlazable bidireccionalmente de valores seleccionados | + // Cierra el dropdown + await select.close(); -Cuando `multi` es true, los usuarios pueden seleccionar múltiples opciones usando Espacio para alternar selección. El popup permanece abierto después de la selección, permitiendo opciones adicionales. + // Verifica que el valor se actualizó (ej. lista separada por comas o conteo) + expect(await (await select.host()).text()).toContain('Option 1, Option 2'); + }); +}); +``` -Consulta la [documentación del API de Listbox](guide/aria/listbox#apis) para detalles completos sobre configuración de listbox, modos de selección y propiedades de opciones. +## API reference -### Posicionamiento +Para documentación de API detallada, inspecciona las siguientes referencias de API: -El patrón de multiselect se integra con [CDK Overlay](api/cdk/overlay/CdkConnectedOverlay) para posicionamiento inteligente. Usa `cdkConnectedOverlay` para manejar bordes de viewport y desplazamiento automáticamente. +- [`Combobox`](/api/aria/combobox/Combobox) +- [`ComboboxPopup`](/api/aria/combobox/ComboboxPopup) +- [`ComboboxWidget`](/api/aria/combobox/ComboboxWidget) +- [`Listbox`](/api/aria/listbox/Listbox) +- [`Option`](/api/aria/listbox/Option) diff --git a/adev-es/src/content/guide/aria/overview.en.md b/adev-es/src/content/guide/aria/overview.en.md index 9536e598..159425cd 100644 --- a/adev-es/src/content/guide/aria/overview.en.md +++ b/adev-es/src/content/guide/aria/overview.en.md @@ -3,15 +3,26 @@ ## What is Angular Aria? -Building accessible components seems straightforward, but implementing them according to the W3C Accessibility Guidelines requires significant effort and accessibility expertise. +Building accessible components seems straightforward, but implementing them according to the [W3C Accessibility Guidelines](https://www.w3.org/TR/wcag/) requires significant effort and accessibility expertise. -Angular Aria is a collection of headless, accessible directives that implement common WAI-ARIA patterns. The directives handle keyboard interactions, ARIA attributes, focus management, and screen reader support. All you have to do is provide the HTML structure, CSS styling, and business logic! +Angular Aria is a collection of headless, accessible directives that implement common [WAI-ARIA patterns](https://www.w3.org/WAI/ARIA/apg/patterns/). The directives handle keyboard interactions, ARIA attributes, focus management, and screen reader support. All you have to do is provide the HTML structure, CSS styling, and business logic! ## Installation -```shell -npm install @angular/aria -``` + + + npm install @angular/aria + + + yarn add @angular/aria + + + pnpm add @angular/aria + + + bun add @angular/aria + + ## Showcase diff --git a/adev-es/src/content/guide/aria/overview.md b/adev-es/src/content/guide/aria/overview.md index e378543b..efe85ff2 100644 --- a/adev-es/src/content/guide/aria/overview.md +++ b/adev-es/src/content/guide/aria/overview.md @@ -1,19 +1,19 @@ -## ¿Qué es Angular Aria? +## ¿Qué es Angular Aria? {#what-is-angular-aria} -Construir componentes accesibles parece sencillo, pero implementarlos según las Directrices de Accesibilidad W3C requiere esfuerzo significativo y experiencia en accesibilidad. +Construir componentes accesibles parece sencillo, pero implementarlos según las [Directrices de Accesibilidad W3C](https://www.w3.org/TR/wcag/) requiere esfuerzo significativo y experiencia en accesibilidad. -Angular Aria es una colección de directivas headless y accesibles que implementan patrones WAI-ARIA comunes. Las directivas manejan interacciones de teclado, atributos ARIA, gestión de foco y soporte para lectores de pantalla. Todo lo que tienes que hacer es proporcionar la estructura HTML, estilos CSS y lógica de negocio. +Angular Aria es una colección de directivas headless y accesibles que implementan [patrones WAI-ARIA](https://www.w3.org/WAI/ARIA/apg/patterns/) comunes. Las directivas manejan interacciones de teclado, atributos ARIA, gestión de foco y soporte para lectores de pantalla. Todo lo que tienes que hacer es proporcionar la estructura HTML, estilos CSS y lógica de negocio. -## Instalación +## Instalación {#installation} ```shell npm install @angular/aria ``` -## Demostración +## Demostración {#showcase} Por ejemplo, tomemos un menú de toolbar. Aunque puede parecer una "simple" fila de botones vinculados con lógica específica, la navegación por teclado y los lectores de pantalla agregan mucha complejidad inesperada para quienes no están familiarizados con la accesibilidad. @@ -50,11 +50,11 @@ En este escenario, los desarrolladores necesitan considerar: - **La gestión de foco** necesita moverse lógicamente entre el trigger y los elementos del menú. - **Los idiomas de derecha a izquierda** requieren la capacidad de navegar en reversa. -## ¿Qué está incluido? +## ¿Qué está incluido? {#whats-included} Angular Aria incluye directivas con documentación completa, ejemplos funcionales y referencias de API para patrones interactivos comunes: -### Búsqueda y selección +### Búsqueda y selección {#search-and-selection} | Componente | Descripción | | --------------------------------------- | ---------------------------------------------------------------- | @@ -64,7 +64,7 @@ Angular Aria incluye directivas con documentación completa, ejemplos funcionale | [Multiselect](guide/aria/multiselect) | Patrón de dropdown de selección múltiple con visualización compacta | | [Combobox](guide/aria/combobox) | Directiva primitiva que coordina una entrada de texto con un popup | -### Navegación y llamadas a acción +### Navegación y llamadas a acción {#navigation-and-call-to-actions} | Componente | Descripción | | ----------------------------- | -------------------------------------------------------------- | @@ -72,7 +72,7 @@ Angular Aria incluye directivas con documentación completa, ejemplos funcionale | [Menubar](guide/aria/menubar) | Barra de navegación horizontal para menús de aplicación persistentes | | [Toolbar](guide/aria/toolbar) | Conjuntos agrupados de controles con navegación lógica por teclado | -### Organización de contenido +### Organización de contenido {#content-organization} | Componente | Descripción | | --------------------------------- | ----------------------------------------------------------------------------- | @@ -81,7 +81,7 @@ Angular Aria incluye directivas con documentación completa, ejemplos funcionale | [Tree](guide/aria/tree) | Listas jerárquicas con funcionalidad de expandir/colapsar | | [Grid](guide/aria/grid) | Visualización de datos bidimensional con navegación celda por celda por teclado | -## Cuándo usar Angular Aria +## Cuándo usar Angular Aria {#when-to-use-angular-aria} Angular Aria funciona bien cuando necesitas componentes interactivos accesibles que sean compatibles con WCAG con estilos personalizados. Los ejemplos incluyen: @@ -89,7 +89,7 @@ Angular Aria funciona bien cuando necesitas componentes interactivos accesibles - **Bibliotecas de componentes empresariales** - Estás creando componentes reutilizables para múltiples aplicaciones dentro de una organización - **Requisitos de marca personalizados** - La interfaz necesita coincidir con especificaciones de diseño precisas que las bibliotecas de componentes pre-estilizadas no pueden acomodar fácilmente -## Cuándo no usar Angular Aria +## Cuándo no usar Angular Aria {#when-not-to-use-angular-aria} Angular Aria puede no encajar en todos los escenarios: @@ -97,6 +97,6 @@ Angular Aria puede no encajar en todos los escenarios: - **Formularios simples** - Los controles de formulario HTML nativos como ` -
    +
    @@ -222,56 +214,53 @@ Toolbars automatically support right-to-left languages. Wrap the toolbar in a co -## APIs +## Testing -### Toolbar Directive +Angular Aria provides component harnesses for testing toolbar components. +Here is an example of how to use the harnesses in a component test: -The `ngToolbar` directive provides the container for toolbar functionality. +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ToolbarHarness} from '@angular/aria/toolbar/testing'; +import {MyToolbarComponent} from './my-toolbar'; // Your component -#### Inputs +describe('MyToolbarComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -| Property | Type | Default | Description | -| -------------- | ------------------------------ | -------------- | ------------------------------------------------------ | -| `orientation` | `'vertical'` \| `'horizontal'` | `'horizontal'` | Whether toolbar is vertically or horizontally oriented | -| `disabled` | `boolean` | `false` | Disables the entire toolbar | -| `softDisabled` | `boolean` | `true` | Whether disabled items can receive focus | -| `wrap` | `boolean` | `true` | Whether focus should wrap at the edges | + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyToolbarComponent], + }); -### ToolbarWidget Directive + fixture = TestBed.createComponent(MyToolbarComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -The `ngToolbarWidget` directive marks an element as a navigable widget within the toolbar. + it('should have widgets and allow selection', async () => { + // Load the toolbar harness + const toolbar = await loader.getHarness(ToolbarHarness); -#### Inputs + // Get all widgets + const widgets = await toolbar.getWidgets(); + expect(widgets.length).toBe(3); -| Property | Type | Default | Description | -| ---------- | --------- | ------- | ----------------------------------------------- | -| `id` | `string` | auto | Unique identifier for the widget | -| `disabled` | `boolean` | `false` | Disables the widget | -| `value` | `V` | - | The value associated with the widget (required) | + // Click the first widget + await widgets[0].click(); -#### Signals - -| Property | Type | Description | -| ---------- | ----------------- | ------------------------------------------- | -| `active` | `Signal` | Whether the widget is currently focused | -| `selected` | `Signal` | Whether the widget is selected (in a group) | - -### ToolbarWidgetGroup Directive - -The `ngToolbarWidgetGroup` directive groups related widgets together. - -#### Inputs - -| Property | Type | Default | Description | -| ---------- | --------- | ------- | ---------------------------------------- | -| `disabled` | `boolean` | `false` | Disables all widgets in the group | -| `multi` | `boolean` | `false` | Whether multiple widgets can be selected | + // Verify selection state + expect(await widgets[0].isSelected()).toBe(true); + }); +}); +``` -### Related components +## API reference -Toolbar can contain various widget types including buttons, trees, and comboboxes. See individual component documentation for specific widget implementations. +For detailed API documentation, inspect the following API references: - - - - +- [`Toolbar`](/api/aria/toolbar/Toolbar) +- [`ToolbarWidget`](/api/aria/toolbar/ToolbarWidget) +- [`ToolbarWidgetGroup`](/api/aria/toolbar/ToolbarWidgetGroup) diff --git a/adev-es/src/content/guide/aria/toolbar.md b/adev-es/src/content/guide/aria/toolbar.md index b7e55090..af0d03ba 100644 --- a/adev-es/src/content/guide/aria/toolbar.md +++ b/adev-es/src/content/guide/aria/toolbar.md @@ -6,7 +6,7 @@ -## Visión general +## Visión general {#overview} Un contenedor para agrupar controles y acciones relacionados con navegación por teclado, comúnmente usado para formato de texto, toolbars y paneles de comandos. @@ -36,7 +36,7 @@ Un contenedor para agrupar controles y acciones relacionados con navegación por -## Uso +## Uso {#usage} Toolbar funciona mejor para agrupar controles relacionados a los que los usuarios acceden frecuentemente. Considera usar toolbar cuando: @@ -51,7 +51,7 @@ Evita toolbar cuando: - Los controles no están relacionados - Toolbar implica una agrupación lógica; controles no relacionados confunden a los usuarios - Navegación anidada compleja - Las jerarquías profundas se sirven mejor con menús o componentes de navegación -## Características +## Características {#features} El toolbar de Angular proporciona una implementación de toolbar completamente accesible con: @@ -63,9 +63,9 @@ El toolbar de Angular proporciona una implementación de toolbar completamente a - **Soporte de Texto Bidireccional** - Maneja automáticamente idiomas de derecha a izquierda (RTL) - **Foco Configurable** - Elige entre navegación envolvente o paradas duras en los bordes -## Ejemplos +## Ejemplos {#examples} -### Toolbar horizontal básico +### Toolbar horizontal básico {#basic-horizontal-toolbar} Los toolbars horizontales organizan controles de izquierda a derecha, coincidiendo con el patrón común en editores de texto y herramientas de diseño. Las teclas de flecha navegan entre widgets, manteniendo el foco dentro del toolbar hasta que los usuarios presionen Tab para moverse al siguiente elemento de la página. @@ -95,7 +95,7 @@ Los toolbars horizontales organizan controles de izquierda a derecha, coincidien -### Toolbar vertical +### Toolbar vertical {#vertical-toolbar} Los toolbars verticales apilan controles de arriba a abajo, útil para paneles laterales o paletas de comandos verticales. Las teclas de flecha arriba y abajo navegan entre widgets. @@ -125,7 +125,7 @@ Los toolbars verticales apilan controles de arriba a abajo, útil para paneles l -### Grupos de widgets +### Grupos de widgets {#widget-groups} Los grupos de widgets contienen controles relacionados que funcionan juntos, como opciones de alineación de texto u opciones de formato de lista. Los grupos mantienen su propio estado interno mientras participan en la navegación del toolbar. @@ -157,7 +157,7 @@ El input `multi` controla si múltiples widgets dentro de un grupo pueden ser se
    ``` -### Widgets deshabilitados +### Widgets deshabilitados {#disabled-widgets} Los toolbars soportan dos modos deshabilitados: @@ -192,7 +192,7 @@ Por defecto, `softDisabled` es `true`, lo que permite que los widgets deshabilit -### Soporte de derecha a izquierda (RTL) +### Soporte de derecha a izquierda (RTL) {#right-to-left-rtl-support} Los toolbars soportan automáticamente idiomas de derecha a izquierda. Envuelve el toolbar en un contenedor con `dir="rtl"` para invertir el diseño y dirección de navegación por teclado. La navegación por teclas de flecha se ajusta automáticamente: la flecha izquierda se mueve al siguiente widget, la flecha derecha al anterior. @@ -222,56 +222,53 @@ Los toolbars soportan automáticamente idiomas de derecha a izquierda. Envuelve -## APIs +## Testing -### Directiva Toolbar +Angular Aria proporciona harnesses de componentes para probar componentes toolbar. +Aquí hay un ejemplo de cómo usar los harnesses en una prueba de componente: -La directiva `ngToolbar` proporciona el contenedor para la funcionalidad de toolbar. +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {ToolbarHarness} from '@angular/aria/toolbar/testing'; +import {MyToolbarComponent} from './my-toolbar'; // Tu componente -#### Inputs +describe('MyToolbarComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; -| Propiedad | Tipo | Por defecto | Descripción | -| -------------- | ------------------------------ | -------------- | ----------------------------------------------------- | -| `orientation` | `'vertical'` \| `'horizontal'` | `'horizontal'` | Si el toolbar está orientado vertical u horizontalmente | -| `disabled` | `boolean` | `false` | Deshabilita todo el toolbar | -| `softDisabled` | `boolean` | `true` | Si los elementos deshabilitados pueden recibir foco | -| `wrap` | `boolean` | `true` | Si el foco debe envolver en los bordes | + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyToolbarComponent], + }); -### Directiva ToolbarWidget + fixture = TestBed.createComponent(MyToolbarComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); -La directiva `ngToolbarWidget` marca un elemento como un widget navegable dentro del toolbar. + it('should have widgets and allow selection', async () => { + // Carga el harness del toolbar + const toolbar = await loader.getHarness(ToolbarHarness); -#### Inputs + // Obtiene todos los widgets + const widgets = await toolbar.getWidgets(); + expect(widgets.length).toBe(3); -| Propiedad | Tipo | Por defecto | Descripción | -| ---------- | --------- | ----------- | ---------------------------------------------- | -| `id` | `string` | auto | Identificador único para el widget | -| `disabled` | `boolean` | `false` | Deshabilita el widget | -| `value` | `V` | - | El valor asociado con el widget (requerido) | + // Hace clic en el primer widget + await widgets[0].click(); -#### Signals - -| Propiedad | Tipo | Descripción | -| ---------- | ----------------- | ------------------------------------------------ | -| `active` | `Signal` | Si el widget está enfocado actualmente | -| `selected` | `Signal` | Si el widget está seleccionado (en un grupo) | - -### Directiva ToolbarWidgetGroup - -La directiva `ngToolbarWidgetGroup` agrupa widgets relacionados juntos. - -#### Inputs - -| Propiedad | Tipo | Por defecto | Descripción | -| ---------- | --------- | ----------- | ----------------------------------------------- | -| `disabled` | `boolean` | `false` | Deshabilita todos los widgets del grupo | -| `multi` | `boolean` | `false` | Si múltiples widgets pueden ser seleccionados | + // Verifica el estado de selección + expect(await widgets[0].isSelected()).toBe(true); + }); +}); +``` -### Componentes relacionados +## API reference -Toolbar puede contener varios tipos de widget incluyendo botones, árboles y comboboxes. Consulta la documentación de componentes individuales para implementaciones específicas de widget. +Para documentación de API detallada, inspecciona las siguientes referencias de API: - - - - +- [`Toolbar`](/api/aria/toolbar/Toolbar) +- [`ToolbarWidget`](/api/aria/toolbar/ToolbarWidget) +- [`ToolbarWidgetGroup`](/api/aria/toolbar/ToolbarWidgetGroup) diff --git a/adev-es/src/content/guide/aria/tree.en.md b/adev-es/src/content/guide/aria/tree.en.md index 712ab9cc..9e7b9340 100644 --- a/adev-es/src/content/guide/aria/tree.en.md +++ b/adev-es/src/content/guide/aria/tree.en.md @@ -157,74 +157,68 @@ Disable specific tree nodes to prevent interaction. Control whether disabled ite When `[softDisabled]="true"` on the tree, disabled items can receive focus but cannot be activated or selected. When `[softDisabled]="false"`, disabled items are skipped during keyboard navigation. -## APIs - -### Tree - -The container directive that manages hierarchical navigation and selection. - -#### Inputs - -| Property | Type | Default | Description | -| --------------- | -------------------------------- | ------------ | ------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Disables the entire tree | -| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable but not interactive | -| `multi` | `boolean` | `false` | Whether multiple items can be selected | -| `selectionMode` | `'explicit' \| 'follow'` | `'explicit'` | Whether selection requires explicit action or follows focus | -| `nav` | `boolean` | `false` | Whether the tree is in navigation mode (uses `aria-current`) | -| `wrap` | `boolean` | `true` | Whether keyboard navigation wraps from last to first item | -| `focusMode` | `'roving' \| 'activedescendant'` | `'roving'` | Focus strategy used by the tree | -| `values` | `any[]` | `[]` | Selected item values (supports two-way binding) | - -#### Methods - -| Method | Parameters | Description | -| ---------------- | ---------- | --------------------------------------------- | -| `expandAll` | none | Expands all tree nodes | -| `collapseAll` | none | Collapses all tree nodes | -| `selectAll` | none | Selects all items (only in multi-select mode) | -| `clearSelection` | none | Clears all selection | - -### TreeItem - -An individual node in the tree that can contain child nodes. - -#### Inputs - -| Property | Type | Default | Description | -| ---------- | --------- | ------- | ------------------------------------------------------- | -| `value` | `any` | — | **Required.** Unique value for this tree item | -| `disabled` | `boolean` | `false` | Disables this item | -| `expanded` | `boolean` | `false` | Whether the node is expanded (supports two-way binding) | - -#### Signals - -| Property | Type | Description | -| ------------- | ----------------- | ------------------------------------ | -| `selected` | `Signal` | Whether the item is selected | -| `active` | `Signal` | Whether the item currently has focus | -| `hasChildren` | `Signal` | Whether the item has child nodes | - -#### Methods - -| Method | Parameters | Description | -| ---------- | ---------- | --------------------------- | -| `expand` | none | Expands this node | -| `collapse` | none | Collapses this node | -| `toggle` | none | Toggles the expansion state | - -### TreeGroup +## Testing + +Angular Aria provides component harnesses for testing tree components. +Here is an example of how to use the harnesses in a component test: + +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {TreeHarness} from '@angular/aria/tree/testing'; +import {MyTreeComponent} from './my-tree'; // Your component + +describe('MyTreeComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyTreeComponent], + }); + + fixture = TestBed.createComponent(MyTreeComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should navigate and expand tree items', async () => { + const tree = await loader.getHarness(TreeHarness); + + // Get top-level structure representation + expect(await tree.getTreeStructure()).toEqual({ + children: [{text: 'public'}, {text: 'src'}, {text: 'package.json'}], + }); + + // Get all items (currently visible) + const items = await tree.getItems(); + expect(items.length).toBe(3); + + // Expand the first item ('public') + expect(await items[0].isExpanded()).toBe(false); + await items[0].click(); + expect(await items[0].isExpanded()).toBe(true); + + // Verifying tree structure updates after expansion + expect(await tree.getTreeStructure()).toEqual({ + children: [ + { + text: 'public', + children: [{text: 'index.html'}, {text: 'styles.css'}], + }, + {text: 'src'}, + {text: 'package.json'}, + ], + }); + }); +}); +``` -A container for child tree items. +## API reference -This directive has no inputs, outputs, or methods. It serves as a container to organize child `ngTreeItem` elements: +For detailed API documentation, inspect the following API references: -```angular-html -
  • - Parent Item -
      -
    • Child 1
    • -
    • Child 2
    • -
    -
  • -``` +- [`Tree`](/api/aria/tree/Tree) +- [`TreeItem`](/api/aria/tree/TreeItem) +- [`TreeItemGroup`](/api/aria/tree/TreeItemGroup) diff --git a/adev-es/src/content/guide/aria/tree.md b/adev-es/src/content/guide/aria/tree.md index 9450ac2f..6eab94a3 100644 --- a/adev-es/src/content/guide/aria/tree.md +++ b/adev-es/src/content/guide/aria/tree.md @@ -6,7 +6,7 @@ -## Visión general +## Visión general {#overview} Un árbol muestra datos jerárquicos donde los elementos pueden expandirse para revelar hijos o colapsar para ocultarlos. Los usuarios navegan con teclas de flecha, expanden y colapsan nodos y opcionalmente seleccionan elementos para escenarios de navegación o selección de datos. @@ -16,7 +16,7 @@ Un árbol muestra datos jerárquicos donde los elementos pueden expandirse para -## Uso +## Uso {#usage} Los árboles funcionan bien para mostrar datos jerárquicos donde los usuarios necesitan navegar a través de estructuras anidadas. @@ -36,7 +36,7 @@ Los árboles funcionan bien para mostrar datos jerárquicos donde los usuarios n - Crear dropdowns simples (usa [Select](guide/aria/select) en su lugar) - Construir navegación breadcrumb (usa patrones breadcrumb) -## Características +## Características {#features} - **Navegación jerárquica** - Estructura de árbol anidada con funcionalidad de expandir y colapsar - **Modos de selección** - Selección simple o múltiple con comportamiento explícito o de seguir foco @@ -47,9 +47,9 @@ Los árboles funcionan bien para mostrar datos jerárquicos donde los usuarios n - **Modos de foco** - Estrategias de foco roving tabindex o activedescendant - **Soporte RTL** - Navegación para idiomas de derecha a izquierda -## Ejemplos +## Ejemplos {#examples} -### Árbol de navegación +### Árbol de navegación {#navigation-tree} Usa un árbol para navegación donde hacer clic en elementos activa acciones en lugar de seleccionarlos. @@ -65,7 +65,7 @@ Usa un árbol para navegación donde hacer clic en elementos activa acciones en Establece `[nav]="true"` para habilitar el modo de navegación. Esto usa `aria-current` para indicar la página actual en lugar de selección. -### Selección simple +### Selección simple {#single-selection} Habilita selección simple para escenarios donde los usuarios eligen un elemento del árbol. @@ -88,7 +88,7 @@ Habilita selección simple para escenarios donde los usuarios eligen un elemento Deja `[multi]="false"` (el valor predeterminado) para selección simple. Los usuarios presionan Espacio para seleccionar el elemento enfocado. -### Multi-selección +### Multi-selección {#multi-selection} Permite a los usuarios seleccionar múltiples elementos del árbol. @@ -111,7 +111,7 @@ Permite a los usuarios seleccionar múltiples elementos del árbol. Establece `[multi]="true"` en el árbol. Los usuarios seleccionan elementos individualmente con Espacio o seleccionan rangos con Shift+teclas de flecha. -### La selección sigue al foco +### La selección sigue al foco {#selection-follows-focus} Cuando la selección sigue al foco, el elemento enfocado se selecciona automáticamente. Esto simplifica la interacción para escenarios de navegación. @@ -134,7 +134,7 @@ Cuando la selección sigue al foco, el elemento enfocado se selecciona automáti Establece `[selectionMode]="'follow'"` en el árbol. La selección se actualiza automáticamente mientras los usuarios navegan con teclas de flecha. -### Elementos de árbol deshabilitados +### Elementos de árbol deshabilitados {#disabled-tree-items} Deshabilita nodos de árbol específicos para prevenir interacción. Controla si los elementos deshabilitados pueden recibir foco. @@ -157,74 +157,68 @@ Deshabilita nodos de árbol específicos para prevenir interacción. Controla si Cuando `[softDisabled]="true"` en el árbol, los elementos deshabilitados pueden recibir foco pero no pueden ser activados o seleccionados. Cuando `[softDisabled]="false"`, los elementos deshabilitados se omiten durante la navegación por teclado. -## APIs - -### Tree - -La directiva contenedor que gestiona navegación y selección jerárquica. - -#### Inputs - -| Propiedad | Tipo | Por defecto | Descripción | -| --------------- | -------------------------------- | ------------ | ---------------------------------------------------------------- | -| `disabled` | `boolean` | `false` | Deshabilita todo el árbol | -| `softDisabled` | `boolean` | `true` | Cuando es `true`, los elementos deshabilitados son enfocables pero no interactivos | -| `multi` | `boolean` | `false` | Si múltiples elementos pueden ser seleccionados | -| `selectionMode` | `'explicit' \| 'follow'` | `'explicit'` | Si la selección requiere acción explícita o sigue al foco | -| `nav` | `boolean` | `false` | Si el árbol está en modo de navegación (usa `aria-current`) | -| `wrap` | `boolean` | `true` | Si la navegación por teclado se envuelve del último al primer elemento | -| `focusMode` | `'roving' \| 'activedescendant'` | `'roving'` | Estrategia de foco usada por el árbol | -| `values` | `any[]` | `[]` | Valores de elementos seleccionados (soporta enlace bidireccional) | - -#### Métodos - -| Método | Parámetros | Descripción | -| ---------------- | ---------- | -------------------------------------------------- | -| `expandAll` | none | Expande todos los nodos del árbol | -| `collapseAll` | none | Colapsa todos los nodos del árbol | -| `selectAll` | none | Selecciona todos los elementos (solo en modo multi-selección) | -| `clearSelection` | none | Limpia toda selección | - -### TreeItem - -Un nodo individual en el árbol que puede contener nodos hijo. - -#### Inputs - -| Propiedad | Tipo | Por defecto | Descripción | -| ---------- | --------- | ----------- | ---------------------------------------------------------- | -| `value` | `any` | — | **Requerido.** Valor único para este elemento del árbol | -| `disabled` | `boolean` | `false` | Deshabilita este elemento | -| `expanded` | `boolean` | `false` | Si el nodo está expandido (soporta enlace bidireccional) | - -#### Signals - -| Propiedad | Tipo | Descripción | -| ------------- | ----------------- | ------------------------------------------- | -| `selected` | `Signal` | Si el elemento está seleccionado | -| `active` | `Signal` | Si el elemento tiene el foco actualmente | -| `hasChildren` | `Signal` | Si el elemento tiene nodos hijo | - -#### Métodos - -| Método | Parámetros | Descripción | -| ---------- | ---------- | -------------------------------- | -| `expand` | none | Expande este nodo | -| `collapse` | none | Colapsa este nodo | -| `toggle` | none | Alterna el estado de expansión | - -### TreeGroup +## Testing + +Angular Aria proporciona harnesses de componentes para probar componentes tree. +Aquí hay un ejemplo de cómo usar los harnesses en una prueba de componente: + +```typescript +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {HarnessLoader} from '@angular/cdk/testing'; +import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; +import {TreeHarness} from '@angular/aria/tree/testing'; +import {MyTreeComponent} from './my-tree'; // Tu componente + +describe('MyTreeComponent', () => { + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [MyTreeComponent], + }); + + fixture = TestBed.createComponent(MyTreeComponent); + await fixture.whenStable(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should navigate and expand tree items', async () => { + const tree = await loader.getHarness(TreeHarness); + + // Obtiene la representación de la estructura de nivel superior + expect(await tree.getTreeStructure()).toEqual({ + children: [{text: 'public'}, {text: 'src'}, {text: 'package.json'}], + }); + + // Obtiene todos los elementos (actualmente visibles) + const items = await tree.getItems(); + expect(items.length).toBe(3); + + // Expande el primer elemento ('public') + expect(await items[0].isExpanded()).toBe(false); + await items[0].click(); + expect(await items[0].isExpanded()).toBe(true); + + // Verifica que la estructura del árbol se actualiza después de la expansión + expect(await tree.getTreeStructure()).toEqual({ + children: [ + { + text: 'public', + children: [{text: 'index.html'}, {text: 'styles.css'}], + }, + {text: 'src'}, + {text: 'package.json'}, + ], + }); + }); +}); +``` -Un contenedor para elementos de árbol hijo. +## API reference -Esta directiva no tiene inputs, outputs ni métodos. Sirve como un contenedor para organizar elementos `ngTreeItem` hijo: +Para documentación de API detallada, inspecciona las siguientes referencias de API: -```angular-html -
  • - Parent Item -
      -
    • Child 1
    • -
    • Child 2
    • -
    -
  • -``` +- [`Tree`](/api/aria/tree/Tree) +- [`TreeItem`](/api/aria/tree/TreeItem) +- [`TreeItemGroup`](/api/aria/tree/TreeItemGroup) diff --git a/adev-es/src/content/guide/components/advanced-configuration.en.md b/adev-es/src/content/guide/components/advanced-configuration.en.md index 4e71034e..87a47d4e 100644 --- a/adev-es/src/content/guide/components/advanced-configuration.en.md +++ b/adev-es/src/content/guide/components/advanced-configuration.en.md @@ -7,12 +7,12 @@ TIP: This guide assumes you've already read the [Essentials Guide](essentials). The `@Component` decorator accepts a `changeDetection` option that controls the component's **change detection mode**. There are two change detection mode options. -**`ChangeDetectionStrategy.Default`** is, unsurprisingly, the default strategy. In this mode, +**`ChangeDetectionStrategy.Eager`/`Default`** is an optional mode. In this mode, Angular checks whether the component's DOM needs an update whenever any activity may have occurred application-wide. Activities that trigger this checking include user interaction, network response, timers, and more. -**`ChangeDetectionStrategy.OnPush`** is an optional mode that reduces the amount of checking Angular +**`ChangeDetectionStrategy.OnPush`** is the default strategy (since v22). This mode reduces the amount of checking Angular needs to perform. In this mode, the framework only checks if a component's DOM needs an update when: - A component input has changes as a result of a binding in a template, or @@ -40,7 +40,7 @@ import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; @Component({ ..., schemas: [CUSTOM_ELEMENTS_SCHEMA], - template: '' + template: '' }) export class ComponentWithCustomElements { } ``` diff --git a/adev-es/src/content/guide/components/advanced-configuration.md b/adev-es/src/content/guide/components/advanced-configuration.md index a6839152..28282d60 100644 --- a/adev-es/src/content/guide/components/advanced-configuration.md +++ b/adev-es/src/content/guide/components/advanced-configuration.md @@ -28,7 +28,7 @@ Por defecto, Angular elimina y colapsa los espacios en blanco superfluos en las saltos de línea e indentación. Puedes cambiar esta configuración estableciendo explícitamente `preserveWhitespaces` a `true` en los metadatos del componente. -## Esquemas de elementos personalizados +## Esquemas de elementos personalizados {#custom-element-schemas} Por defecto, Angular lanza un error cuando encuentra un elemento HTML desconocido. Puedes deshabilitar este comportamiento para un componente incluyendo `CUSTOM_ELEMENTS_SCHEMA` en la propiedad `schemas` diff --git a/adev-es/src/content/guide/components/anatomy-of-components.en.md b/adev-es/src/content/guide/components/anatomy-of-components.en.md index 5a301b26..e08682b0 100644 --- a/adev-es/src/content/guide/components/anatomy-of-components.en.md +++ b/adev-es/src/content/guide/components/anatomy-of-components.en.md @@ -11,13 +11,13 @@ Every component must have: You provide Angular-specific information for a component by adding a `@Component` [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) on top of the TypeScript class: - +```angular-ts {highlight: [1, 2, 3, 4]} @Component({ selector: 'profile-photo', - template: `Your profile photo`, + template: `Your profile photo`, }) -export class ProfilePhoto { } - +export class ProfilePhoto {} +``` For full details on writing Angular templates, including data binding, event handling, and control flow, see the [Templates guide](guide/templates). @@ -25,29 +25,33 @@ The object passed to the `@Component` decorator is called the component's **meta Components can optionally include a list of CSS styles that apply to that component's DOM: - +```angular-ts {highlight: [4]} @Component({ selector: 'profile-photo', - template: `Your profile photo`, - styles: `img { border-radius: 50%; }`, + template: `Your profile photo`, + styles: ` + img { + border-radius: 50%; + } + `, }) -export class ProfilePhoto { } - +export class ProfilePhoto {} +``` By default, a component's styles only affect elements defined in that component's template. See [Styling Components](guide/components/styling) for details on Angular's approach to styling. You can alternatively choose to write your template and styles in separate files: - +```ts {highlight: [3,4]} @Component({ selector: 'profile-photo', templateUrl: 'profile-photo.html', styleUrl: 'profile-photo.css', }) -export class ProfilePhoto { } - +export class ProfilePhoto {} +``` -This can help separate the concerns of _presentation_ from _behavior_ in your project. You can choose one approach for your entire project, or you decide which to use for each component. +This can help separate the concerns of _presentation_ from _behavior_ in your project. You can choose one approach for your entire project, or you can decide which to use for each component. Both `templateUrl` and `styleUrl` are relative to the directory in which the component resides. @@ -67,41 +71,41 @@ import {ProfilePhoto} from './profile-photo'; imports: [ProfilePhoto], /* ... */ }) -export class UserProfile { } +export class UserProfile {} ``` -By default, Angular components are _standalone_, meaning that you can directly add them to the `imports` array of other components. Components created with an earlier version of Angular may instead specify `standalone: false` in their `@Component` decorator. For these components, you instead import the `NgModule` in which the component is defined. See the full [`NgModule` guide](guide/ngmodules) for details. +By default, Angular components are _standalone_, meaning that you can directly add them to the `imports` array of other components. Components created with an earlier version of Angular may instead specify `standalone: false` in their `@Component` decorator. For these components, you instead import the `NgModule` in which the component is defined. See the full [`NgModule` guide](guide/ngmodules/overview) for details. -Important: In Angular versions before 19.0.0, the `standalone` option defaults to `false`. +IMPORTANT: In Angular versions before 19.0.0, the `standalone` option defaults to `false`. ### Showing components in a template Every component defines a [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors): - +```angular-ts {highlight: [2]} @Component({ selector: 'profile-photo', ... }) export class ProfilePhoto { } - +``` See [Component Selectors](guide/components/selectors) for details about which types of selectors Angular supports and guidance on choosing a selector. You show a component by creating a matching HTML element in the template of _other_ components: - +```angular-ts {highlight: [8]} @Component({ selector: 'profile-photo', }) -export class ProfilePhoto { } +export class ProfilePhoto {} @Component({ -imports: [ProfilePhoto], -template: `` + imports: [ProfilePhoto], + template: ``, }) -export class UserProfile { } - +export class UserProfile {} +``` Angular creates an instance of the component for every matching HTML element it encounters. The DOM element that matches a component's selector is referred to as that component's **host element**. The contents of a component's template are rendered inside its host element. diff --git a/adev-es/src/content/guide/components/anatomy-of-components.md b/adev-es/src/content/guide/components/anatomy-of-components.md index 688adff5..03acc4b3 100644 --- a/adev-es/src/content/guide/components/anatomy-of-components.md +++ b/adev-es/src/content/guide/components/anatomy-of-components.md @@ -51,9 +51,9 @@ Esto puede ayudar a separar las preocupaciones de _presentación_ del _comportam Tanto `templateUrl` como `styleUrls` son relativos al directorio en el que reside el componente. -## Usar Componentes +## Usar Componentes {#using-components} -### Imports in the `@Component` decorator +### Imports in the `@Component` decorator {#imports-in-the-component-decorator} Para usar un component, [directive](guide/directives), o [pipe](guide/templates/pipes), debes agregarlo al arreglo de `imports` en el decorador `@Component`: @@ -70,11 +70,11 @@ import {ProfilePhoto} from './profile-photo'; export class UserProfile { } ``` -Por defecto, los componentes de Angular son _independientes_ (standalone), lo que significa que puedes agregarlos directamente al arreglo `imports` de otros componentes. Los componentes creados con una versión anterior de Angular pueden especificar `standalone: false` en su decorador `@Component`. Para estos componentes, en su lugar importas el `NgModule` en el que está definido el componente. Consulta la [guía completa de `NgModule`](guide/ngmodules) para detalles. +Por defecto, los componentes de Angular son _independientes_ (standalone), lo que significa que puedes agregarlos directamente al arreglo `imports` de otros componentes. Los componentes creados con una versión anterior de Angular pueden especificar `standalone: false` en su decorador `@Component`. Para estos componentes, en su lugar importas el `NgModule` en el que está definido el componente. Consulta la [guía completa de `NgModule`](guide/ngmodules/overview) para detalles. Importante: En versiones de Angular anteriores a 19.0.0, la opción `standalone` por defecto es `false`. -### Mostrar componentes en una plantilla +### Mostrar componentes en una plantilla {#showing-components-in-a-template} Todo componente define un[CSS selector](https://developer.mozilla.org/es/docs/Learn_web_development/Core/Styling_basics/Basic_selectors): diff --git a/adev-es/src/content/guide/components/content-projection.en.md b/adev-es/src/content/guide/components/content-projection.en.md index efa351ca..202af3e3 100644 --- a/adev-es/src/content/guide/components/content-projection.en.md +++ b/adev-es/src/content/guide/components/content-projection.en.md @@ -10,7 +10,9 @@ example, you may want to create a custom card component: selector: 'custom-card', template: '
    ', }) -export class CustomCard {/* ... */} +export class CustomCard { + /* ... */ +} ``` **You can use the `` element as a placeholder to mark where content should go**: @@ -20,7 +22,9 @@ export class CustomCard {/* ... */} selector: 'custom-card', template: '
    ', }) -export class CustomCard {/* ... */} +export class CustomCard { + /* ... */ +} ``` TIP: `` works similarly @@ -40,7 +44,9 @@ rendered, or **projected**, at the location of that ``:
    `, }) -export class CustomCard {/* ... */} +export class CustomCard { + /* ... */ +} ``` ```angular-html @@ -95,13 +101,13 @@ export class CardBody {} ```angular-ts -Component({ +@Component({ selector: 'custom-card', template: `
    - +
    - +
    `, }) @@ -144,10 +150,10 @@ did not match a `select` attribute: ```angular-html
    - +
    - +
    ``` @@ -216,9 +222,9 @@ placeholder, Angular compares against the `ngProjectAs` value instead of the ele ```angular-html
    - +
    - +
    ``` @@ -243,3 +249,29 @@ placeholder, Angular compares against the `ngProjectAs` value instead of the ele ``` `ngProjectAs` supports only static values and cannot be bound to dynamic expressions. + +## Caveats + +### Projected content lives in the parent's view + +Even though projected content is _rendered_ inside the receiving component, it is still owned by the component that declared it. Angular tracks it as part of the parent's view, which has a couple of side effects worth knowing about. + +**Change detection:** Projected content is checked when the _parent_ runs change detection. If the receiving component uses `OnPush`, Angular can skip checking that component's own template — but it won't skip the projected content, because that belongs to the parent. + +```angular-html + + + + + +``` + +**Dependency injection:** Projected content gets its dependencies from the parent's injector, not from the receiving component's `viewProviders`. See [Providers and viewProviders](guide/di/hierarchical-dependency-injection) for details. + +### Some library components don't support projected children + +Certain components — menus, tabs, lists — use `ContentChildren` to find their children and wire up behavior like keyboard navigation, focus management, or ARIA attributes. They're written assuming they own their children directly, so projecting external content into them tends to break things in subtle ways. + +For example, wrapping `` elements in an extra layer and projecting them into `` can silently break keyboard navigation and screen reader support. The query still finds the items, but the internal setup that makes them interactive may not work correctly when the items come from a different view context. + +If a library component manages its children's behavior, check its docs before reaching for content projection — it may not be supported. diff --git a/adev-es/src/content/guide/components/content-projection.md b/adev-es/src/content/guide/components/content-projection.md index 5d6ccd90..dd918024 100644 --- a/adev-es/src/content/guide/components/content-projection.md +++ b/adev-es/src/content/guide/components/content-projection.md @@ -72,7 +72,7 @@ instancia y crea nodos DOM para el contenido renderizado en un marcador de posic ese marcador de posición `` está oculto. Para renderizado condicional del contenido del componente, consulta [Fragmentos de plantilla](api/core/ng-template). -## Múltiples marcadores de posición de contenido +## Múltiples marcadores de posición de contenido {#multiple-content-placeholders} Angular admite proyectar múltiples elementos diferentes en diferentes marcadores de posición `` basándose en selectores CSS. Expandiendo el ejemplo de la tarjeta de arriba, podrías crear dos marcadores de posición para @@ -174,7 +174,7 @@ no coincidieron con un atributo `select`: Si un componente no incluye un marcador de posición `` sin un atributo `select`, cualquier elemento que no coincida con uno de los marcadores de posición del componente no se renderiza en el DOM. -## Contenido de respaldo +## Contenido de respaldo {#fallback-content} Angular puede mostrar _contenido de respaldo_ para el marcador de posición `` de un componente si ese componente no tiene ningún contenido hijo coincidente. Puedes especificar contenido de respaldo agregando contenido hijo al propio elemento ``. @@ -206,7 +206,7 @@ Angular puede mostrar _contenido de respaldo_ para el marcador de posición ` ``` -## Alias de contenido para proyección +## Alias de contenido para proyección {#aliasing-content-for-projection} Angular admite un atributo especial, `ngProjectAs`, que te permite especificar un selector CSS en cualquier elemento. Siempre que un elemento con `ngProjectAs` se compara contra un marcador de posición ``, @@ -242,3 +242,29 @@ Angular compara contra el valor de `ngProjectAs` en lugar de la identidad del el ``` `ngProjectAs` solo admite valores estáticos y no se puede enlazar a expresiones dinámicas. + +## Advertencias {#caveats} + +### El contenido proyectado vive en la vista del padre {#projected-content-lives-in-the-parents-view} + +Aunque el contenido proyectado se _renderiza_ dentro del componente receptor, sigue siendo propiedad del componente que lo declaró. Angular lo rastrea como parte de la vista del padre, lo que tiene un par de efectos secundarios que vale la pena conocer. + +**Detección de cambios:** El contenido proyectado se verifica cuando el _padre_ ejecuta la detección de cambios. Si el componente receptor usa `OnPush`, Angular puede saltarse la verificación de la propia plantilla de ese componente — pero no se saltará el contenido proyectado, porque ese pertenece al padre. + +```angular-html + + + + + +``` + +**Inyección de dependencias:** El contenido proyectado obtiene sus dependencias del injector del padre, no de los `viewProviders` del componente receptor. Consulta [Providers y viewProviders](guide/di/hierarchical-dependency-injection) para más detalles. + +### Algunos componentes de librería no soportan hijos proyectados {#some-library-components-dont-support-projected-children} + +Ciertos componentes — menús, pestañas, listas — usan `ContentChildren` para encontrar a sus hijos y conectar comportamientos como navegación con teclado, gestión del foco o atributos ARIA. Están escritos asumiendo que son propietarios directos de sus hijos, por lo que proyectar contenido externo en ellos tiende a romper las cosas de maneras sutiles. + +Por ejemplo, envolver elementos `` en una capa extra y proyectarlos en `` puede romper silenciosamente la navegación con teclado y el soporte de lectores de pantalla. La consulta aún encuentra los elementos, pero la configuración interna que los hace interactivos puede no funcionar correctamente cuando los elementos provienen de un contexto de vista diferente. + +Si un componente de librería gestiona el comportamiento de sus hijos, consulta su documentación antes de usar proyección de contenido — puede que no sea compatible. diff --git a/adev-es/src/content/guide/components/dom-apis.en.md b/adev-es/src/content/guide/components/dom-apis.en.md index 68ac11a6..9c774c06 100644 --- a/adev-es/src/content/guide/components/dom-apis.en.md +++ b/adev-es/src/content/guide/components/dom-apis.en.md @@ -7,7 +7,7 @@ directly interact with a component's DOM. Components can inject ElementRef to ge component's host element: ```ts -@Component({...}) +@Component(/* ... */) export class ProfilePhoto { constructor() { const elementRef = inject(ElementRef); @@ -23,7 +23,7 @@ You can use Angular's `afterEveryRender` and `afterNextRender` functions to regi callback** that runs when Angular has finished rendering the page. ```ts -@Component({...}) +@Component(/* ... */) export class ProfilePhoto { constructor() { const elementRef = inject(ElementRef); diff --git a/adev-es/src/content/guide/components/dom-apis.md b/adev-es/src/content/guide/components/dom-apis.md index 318a369a..cb23420e 100644 --- a/adev-es/src/content/guide/components/dom-apis.md +++ b/adev-es/src/content/guide/components/dom-apis.md @@ -48,7 +48,7 @@ garantiza que el DOM de un componente esté completamente renderizado en ningún Además, leer o modificar el DOM durante otros hooks de ciclo de vida puede impactar negativamente el rendimiento de la página causando [layout thrashing](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing). -## Usar el renderer de un componente +## Usar el renderer de un componente {#using-a-components-renderer} Los componentes pueden inyectar una instancia de `Renderer2` para realizar ciertas manipulaciones del DOM que están vinculadas a otras características de Angular. @@ -64,7 +64,7 @@ Aparte de estos dos casos de uso específicos, no hay diferencia entre usar `Ren Las APIs de `Renderer2` no admiten manipulación del DOM en contextos de renderizado del lado del servidor o pre-renderizado en tiempo de compilación. -## Cuándo usar APIs del DOM +## Cuándo usar APIs del DOM {#when-to-use-dom-apis} Aunque Angular maneja la mayoría de las preocupaciones de renderizado, algunos comportamientos pueden requerir el uso de APIs del DOM. Algunos casos de uso comunes incluyen: diff --git a/adev-es/src/content/guide/components/host-elements.en.md b/adev-es/src/content/guide/components/host-elements.en.md index 38f5080b..e78c916f 100644 --- a/adev-es/src/content/guide/components/host-elements.en.md +++ b/adev-es/src/content/guide/components/host-elements.en.md @@ -10,9 +10,7 @@ The contents of a component's template are rendered inside its host element. // Component source @Component({ selector: 'profile-photo', - template: ` - Your profile photo - `, + template: `Your profile photo`, }) export class ProfilePhoto {} ``` @@ -64,6 +62,8 @@ export class CustomSlider { } ``` +NOTE: The global target names that can be used to prefix an event name are `document:`, `window:` and `body:`. + ## The `@HostBinding` and `@HostListener` decorators You can alternatively bind to the host element by applying the `@HostBinding` and `@HostListener` @@ -134,14 +134,14 @@ In cases like this, the following rules determine which value wins: ## Styling with CSS custom properties Developers often rely on [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties) to enable a flexible configuration of their component's styles. -You can set such custom properties on a host element with a [style binding][style binding](guide/templates/binding#css-style-properties). +You can set such custom properties on a host element with a [style binding](guide/templates/binding#css-style-properties). ```angular-ts @Component({ /* ... */ host: { '[style.--my-background]': 'color()', - } + }, }) export class MyComponent { color = signal('lightgreen'); @@ -150,14 +150,14 @@ export class MyComponent { In this example, the `--my-background` CSS custom property is bound to the `color` signal. The value of the custom property will automatically update whenever the `color` signal changes. This will affect the current component and all its children that rely on this custom property. -### Setting custom properties on children compoents +### Setting custom properties on children components Alternatively, it is also possible to set css custom properties on the host element of children components with a [style binding](guide/templates/binding#css-style-properties). ```angular-ts @Component({ selector: 'my-component', - template: ``, + template: ``, }) export class MyComponent { color = signal('lightgreen'); diff --git a/adev-es/src/content/guide/components/host-elements.md b/adev-es/src/content/guide/components/host-elements.md index 920412bf..7860ae23 100644 --- a/adev-es/src/content/guide/components/host-elements.md +++ b/adev-es/src/content/guide/components/host-elements.md @@ -35,7 +35,7 @@ export class ProfilePhoto {} En el ejemplo anterior, `` es el elemento host del componente `ProfilePhoto`. -## Enlazar al elemento host +## Enlazar al elemento host {#binding-to-the-host-element} Un componente puede enlazar propiedades, atributos, estilos y eventos a su elemento host. Esto se comporta de manera idéntica a los enlaces en elementos dentro de la plantilla del componente, pero en su lugar se define con @@ -64,7 +64,9 @@ export class CustomSlider { } ``` -## Los decoradores `@HostBinding` y `@HostListener` +NOTA: Los nombres de destino globales que se pueden usar como prefijo del nombre de un evento son `document:`, `window:` y `body:`. + +## Los decoradores `@HostBinding` y `@HostListener` {#the-hostbinding-and-hostlistener-decorators} Alternativamente puedes enlazar al elemento host aplicando los decoradores `@HostBinding` y `@HostListener` a los miembros de la clase. @@ -105,7 +107,7 @@ export class CustomSlider { decoradores existen exclusivamente por compatibilidad hacia atrás. -## Colisiones de enlaces +## Colisiones de enlaces {#binding-collisions} Cuando usas un componente en una plantilla, puedes agregar enlaces al elemento de esa instancia del componente. El componente _también_ puede definir enlaces host para las mismas propiedades o atributos. @@ -131,7 +133,7 @@ En casos como este, las siguientes reglas determinan qué valor gana: - Si un valor es estático y el otro dinámico, el valor dinámico gana. - Si ambos valores son dinámicos, el enlace host del componente gana. -## Estilizar con propiedades personalizadas CSS +## Estilizar con propiedades personalizadas CSS {#styling-with-css-custom-properties} Los desarrolladores frecuentemente dependen de [Propiedades Personalizadas CSS](https://developer.mozilla.org/es/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties) para habilitar una configuración flexible de los estilos de sus componentes. Puedes establecer tales propiedades personalizadas en un elemento host con un [enlace de estilo](guide/templates/binding#css-style-properties). @@ -150,7 +152,7 @@ export class MyComponent { En este ejemplo, la propiedad personalizada CSS `--my-background` está enlazada a la signal `color`. El valor de la propiedad personalizada se actualizará automáticamente cada vez que la signal `color` cambie. Esto afectará al componente actual y a todos sus hijos que dependan de esta propiedad personalizada. -### Establecer propiedades personalizadas en componentes hijos +### Establecer propiedades personalizadas en componentes hijos {#setting-custom-properties-on-children-components} Alternativamente, también es posible establecer propiedades personalizadas CSS en el elemento host de componentes hijos con un [enlace de estilo](guide/templates/binding#css-style-properties). @@ -164,7 +166,7 @@ export class MyComponent { } ``` -## Inyectar atributos del elemento host +## Inyectar atributos del elemento host {#injecting-host-element-attributes} Los componentes y directivas pueden leer atributos estáticos de su elemento host usando `HostAttributeToken` junto con la función [`inject`](api/core/inject). diff --git a/adev-es/src/content/guide/components/importing.en.md b/adev-es/src/content/guide/components/importing.en.md deleted file mode 100644 index a3bbf85b..00000000 --- a/adev-es/src/content/guide/components/importing.en.md +++ /dev/null @@ -1,35 +0,0 @@ -# Importing and using components - -Tip: This guide assumes you've already read the [Essentials Guide](essentials). Read that first if you're new to Angular. - -Angular supports two ways of making a component available to other components: as a standalone component or in an `NgModule`. - -## Standalone components - -A **standalone component** is a component that sets `standalone: true` in its component metadata. -Standalone components directly import other components, directives, and pipes used in their -templates: - - -@Component({ - standalone: true, - selector: 'profile-photo', -}) -export class ProfilePhoto { } - -@Component({ - standalone: true, - imports: [ProfilePhoto], - template: `` -}) -export class UserProfile { } - - -Standalone components are directly importable into other standalone components. - -The Angular team recommends using standalone components for all new development. - -## NgModules - -Angular code that predates standalone components uses `NgModule` as a mechanism for importing and -using other components. See the full [`NgModule` guide](guide/ngmodules) for details. diff --git a/adev-es/src/content/guide/components/importing.md b/adev-es/src/content/guide/components/importing.md deleted file mode 100644 index 9665bcfc..00000000 --- a/adev-es/src/content/guide/components/importing.md +++ /dev/null @@ -1,35 +0,0 @@ -# Importar y usar componentes - -CONSEJO: Esta guía asume que ya has leído la [Guía de Esenciales](essentials). Lee esa primero si eres nuevo en Angular. - -Angular admite dos formas de hacer que un componente esté disponible para otros componentes: como componente standalone o en un `NgModule`. - -## Componentes standalone - -Un **componente standalone** es un componente que establece `standalone: true` en los metadatos de su componente. -Los componentes standalone importan directamente otros componentes, directivas y pipes usados en sus -plantillas: - - -@Component({ - standalone: true, - selector: 'profile-photo', -}) -export class ProfilePhoto { } - -@Component({ - standalone: true, - imports: [ProfilePhoto], - template: `` -}) -export class UserProfile { } - - -Los componentes standalone son directamente importables en otros componentes standalone. - -El equipo de Angular recomienda usar componentes standalone para todo desarrollo nuevo. - -## NgModules - -El código de Angular anterior a los componentes standalone usa `NgModule` como mecanismo para importar y -usar otros componentes. Consulta la [guía completa de `NgModule`](guide/ngmodules) para más detalles. diff --git a/adev-es/src/content/guide/components/inheritance.en.md b/adev-es/src/content/guide/components/inheritance.en.md index b11af7e5..233db69a 100644 --- a/adev-es/src/content/guide/components/inheritance.en.md +++ b/adev-es/src/content/guide/components/inheritance.en.md @@ -12,7 +12,7 @@ export class ListboxBase { value: string; } -@Component({ ... }) +@Component(/* ... */) export class CustomListbox extends ListboxBase { // CustomListbox inherits the `value` property. } @@ -27,9 +27,7 @@ host bindings, inputs, outputs, lifecycle methods. ```angular-ts @Component({ selector: 'base-listbox', - template: ` - ... - `, + template: ` ... `, host: { '(keydown)': 'handleKey($event)', }, @@ -43,9 +41,7 @@ export class ListboxBase { @Component({ selector: 'custom-listbox', - template: ` - ... - `, + template: ` ... `, host: { '(click)': 'focusActiveOption()', }, @@ -67,15 +63,29 @@ and their own. ### Forwarding injected dependencies -If a base class injects dependencies as constructor parameters, the child class must explicitly class these dependencies to `super`. +When a base class uses `inject()` as a property initializer, the child class inherits the property automatically. No `super` forwarding is needed. ```ts -@Component({ ... }) +@Component(/* ... */) export class ListboxBase { - constructor(private element: ElementRef) { } + protected element = inject(ElementRef); } -@Component({ ... }) +@Component(/* ... */) +export class CustomListbox extends ListboxBase { + // `element` is inherited from `ListboxBase`. +} +``` + +If a base class injects dependencies as constructor parameters, the child class must explicitly pass these dependencies to `super`. + +```ts +@Component(/* ... */) +export class ListboxBase { + constructor(private element: ElementRef) {} +} + +@Component(/* ... */) export class CustomListbox extends ListboxBase { constructor(element: ElementRef) { super(element); @@ -90,7 +100,7 @@ implements `ngOnInit` _overrides_ the base class's implementation. If you want t class's lifecycle method, explicitly call the method with `super`: ```ts -@Component({ ... }) +@Component(/* ... */) export class ListboxBase { protected isInitialized = false; ngOnInit() { @@ -98,7 +108,7 @@ export class ListboxBase { } } -@Component({ ... }) +@Component(/* ... */) export class CustomListbox extends ListboxBase { override ngOnInit() { super.ngOnInit(); diff --git a/adev-es/src/content/guide/components/inheritance.md b/adev-es/src/content/guide/components/inheritance.md index 51fc165b..a221c4d2 100644 --- a/adev-es/src/content/guide/components/inheritance.md +++ b/adev-es/src/content/guide/components/inheritance.md @@ -17,7 +17,7 @@ export class CustomListbox extends ListboxBase { } ``` -## Extender otros componentes y directivas +## Extender otros componentes y directivas {#extending-other-components-and-directives} Cuando un componente extiende otro componente o una directiva, hereda algunos de los metadatos definidos en el decorador de la clase base y los miembros decorados de la clase base. Esto incluye @@ -64,7 +64,21 @@ y `disabled`) y dos event listeners (`keydown` y `click`). Las clases hijas terminan con la _unión_ de todos los inputs, outputs y enlaces host de sus ancestros y los suyos propios. -### Reenviar dependencias inyectadas +### Reenviar dependencias inyectadas {#forwarding-injected-dependencies} + +Cuando una clase base usa `inject()` como inicializador de propiedad, la clase hija hereda la propiedad automáticamente. No se necesita reenvío con `super`. + +```ts +@Component(/* ... */) +export class ListboxBase { + protected element = inject(ElementRef); +} + +@Component(/* ... */) +export class CustomListbox extends ListboxBase { + // `element` es heredado de `ListboxBase`. +} +``` Si una clase base inyecta dependencias como parámetros del constructor, la clase hija debe pasar explícitamente estas dependencias a `super`. @@ -82,7 +96,7 @@ export class CustomListbox extends ListboxBase { } ``` -### Sobrescribir métodos de ciclo de vida +### Sobrescribir métodos de ciclo de vida {#overriding-lifecycle-methods} Si una clase base define un método de ciclo de vida, como `ngOnInit`, una clase hija que también implementa `ngOnInit` _sobrescribe_ la implementación de la clase base. Si quieres preservar el diff --git a/adev-es/src/content/guide/components/inputs.en.md b/adev-es/src/content/guide/components/inputs.en.md index 73651d75..caaa2c36 100644 --- a/adev-es/src/content/guide/components/inputs.en.md +++ b/adev-es/src/content/guide/components/inputs.en.md @@ -7,10 +7,10 @@ TIP: If you're familiar with other web frameworks, input properties are similar When you use a component, you commonly want to pass some data to it. A component specifies the data that it accepts by declaring **inputs**: -```ts {highlight:[5]} +```ts {highlight:[6]} import {Component, input} from '@angular/core'; -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { // Declare an input named 'value' with a default value of zero. value = input(0); @@ -26,7 +26,7 @@ This lets you bind to the property in a template: If an input has a default value, TypeScript infers the type from the default value: ```ts -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { // TypeScript infers that this input is a number, returning InputSignal. value = input(0); @@ -38,7 +38,7 @@ You can explicitly declare a type for the input by specifying a generic paramete If an input without a default value is not set, its value is `undefined`: ```ts -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { // Produces an InputSignal because `value` may not be set. value = input(); @@ -57,10 +57,10 @@ When extending a component class, **inputs are inherited by the child class.** The `input` function returns an `InputSignal`. You can read the value by calling the signal: -```ts {highlight:[5]} +```ts {highlight:[9]} import {Component, input, computed} from '@angular/core'; -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { // Declare an input named 'value' with a default value of zero. value = input(0); @@ -76,8 +76,8 @@ Signals created by the `input` function are read-only. You can declare that an input is `required` by calling `input.required` instead of `input`: -```ts {highlight:[3]} -@Component({/*...*/}) +```ts {highlight:[4]} +@Component(/* ... */) export class CustomSlider { // Declare a required input named value. Returns an `InputSignal`. value = input.required(); @@ -127,7 +127,7 @@ The most common use-case for input transforms is to accept a wider range of valu When you specify an input transform, the type of the transform function's parameter determines the types of values that can be set to the input in a template. ```ts -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { widthPx = input('', {transform: appendPx}); } @@ -146,7 +146,7 @@ Angular includes two built-in transform functions for the two most common scenar ```ts import {Component, input, booleanAttribute, numberAttribute} from '@angular/core'; -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { disabled = input(false, {transform: booleanAttribute}); value = input(0, {transform: numberAttribute}); @@ -163,7 +163,7 @@ _presence_ of the attribute indicates a "true" value. However, Angular's `boolea You can specify the `alias` option to change the name of an input in templates. ```ts {highlight:[3]} -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { value = input(0, {alias: 'sliderValue'}); } @@ -186,14 +186,14 @@ When creating a component, you can define a model input similarly to how you cre Both types of input allow someone to bind a value into the property. However, **model inputs allow the component author to write values into the property**. If the property is bound with a two-way binding, the new value propagates to that binding. ```ts -@Component({ /* ... */}) +@Component(/* ... */) export class CustomSlider { // Define a model input named "value". value = model(0); increment() { // Update the model input with a new value, propagating the value to any bindings. - this.value.update(oldValue => oldValue + 10); + this.value.update((oldValue) => oldValue + 10); } } @@ -212,7 +212,7 @@ export class MediaControls { In the above example, the `CustomSlider` can write values into its `value` model input, which then propagates those values back to the `volume` signal in `MediaControls`. This binding keeps the values of `value` and `volume` in sync. Notice that the binding passes the `volume` signal instance, not the _value_ of the signal. -In other respects, model inputs work similarly to standard inputs. You can read the value by calling the signal function, including in reactive contexts like `computed` and `effect`. +In other respects, model inputs work similarly to standard inputs. You can read the value by calling the signal function, including in [reactive contexts](guide/signals#reactive-contexts) like `computed` and `effect`. See [Two-way binding](guide/templates/two-way-binding) for more details on two-way binding in templates. @@ -239,7 +239,7 @@ In the example above, the `CustomSlider` can write values into its `value` model When you declare a model input in a component or directive, Angular automatically creates a corresponding [output](guide/components/outputs) for that model. The output's name is the model input's name suffixed with "Change". ```ts -@Directive({ /* ... */ }) +@Directive(/* ... */) export class CustomCheckbox { // This automatically creates an output named "checkedChange". // Can be subscribed to using `(checkedChange)="handler()"` in the template. @@ -253,7 +253,7 @@ See [Custom events with outputs](guide/components/outputs) for more details on o ### Customizing model inputs -You can mark a model input as required or provide an alias in the same way as a [standard input](guide/signals/inputs). +You can mark a model input as required or provide an alias in the same way as a [standard input](guide/components/inputs). Model inputs do not support input transforms. @@ -274,7 +274,7 @@ TIP: While the Angular team recommends using the signal-based `input` function f You can alternatively declare component inputs by adding the `@Input` decorator to a property: ```ts {highlight:[3]} -@Component({...}) +@Component(/* ... */) export class CustomSlider { @Input() value = 0; } @@ -295,7 +295,7 @@ The `@Input` decorator accepts a config object that lets you change the way that You can specify the `required` option to enforce that a given input must always have a value. ```ts {highlight:[3]} -@Component({...}) +@Component(/* ... */) export class CustomSlider { @Input({required: true}) value = 0; } @@ -326,7 +326,7 @@ function trimString(value: string | undefined) { You can specify the `alias` option to change the name of an input in templates. ```ts {highlight:[3]} -@Component({...}) +@Component(/* ... */) export class CustomSlider { @Input({alias: 'sliderValue'}) value = 0; } @@ -351,7 +351,9 @@ export class CustomSlider { return this.internalValue; } - set value(newValue: number) { this.internalValue = newValue; } + set value(newValue: number) { + this.internalValue = newValue; + } private internalValue = 0; } diff --git a/adev-es/src/content/guide/components/inputs.md b/adev-es/src/content/guide/components/inputs.md index 79c0aa3a..3159c06c 100644 --- a/adev-es/src/content/guide/components/inputs.md +++ b/adev-es/src/content/guide/components/inputs.md @@ -53,7 +53,7 @@ Al extender una clase de componente, **los inputs son heredados por la clase hij **Los nombres de input distinguen entre mayúsculas y minúsculas.** -## Leer inputs +## Leer inputs {#reading-inputs} La función `input` devuelve un `InputSignal`. Puedes leer el valor llamando a la signal: @@ -72,7 +72,7 @@ export class CustomSlider { Las signals creadas por la función `input` son de solo lectura. -## Inputs requeridos +## Inputs requeridos {#required-inputs} Puedes declarar que un input es `required` llamando a `input.required` en lugar de `input`: @@ -88,11 +88,11 @@ Angular exige que los inputs requeridos _deben_ establecerse cuando el component Los inputs requeridos no incluyen automáticamente `undefined` en el parámetro genérico del `InputSignal` devuelto. -## Configurar inputs +## Configurar inputs {#configuring-inputs} La función `input` acepta un objeto de configuración como segundo parámetro que te permite cambiar la forma en que funciona el input. -### Transformaciones de input +### Transformaciones de input {#input-transforms} Puedes especificar una función `transform` para cambiar el valor de un input cuando es establecido por Angular. @@ -122,7 +122,7 @@ El caso de uso más común para las transformaciones de input es aceptar un rang **Las funciones de transformación de input siempre deben ser [funciones puras](https://en.wikipedia.org/wiki/Pure_function).** Depender de estado fuera de la función de transformación puede llevar a comportamiento impredecible. -#### Verificación de tipos +#### Verificación de tipos {#type-checking} Cuando especificas una transformación de input, el tipo del parámetro de la función de transformación determina los tipos de valores que se pueden establecer al input en una plantilla. @@ -139,7 +139,7 @@ function appendPx(value: number): string { En el ejemplo anterior, el input `widthPx` acepta un `number` mientras que la propiedad `InputSignal` devuelve un `string`. -#### Transformaciones integradas +#### Transformaciones integradas {#built-in-transformations} Angular incluye dos funciones de transformación integradas para los dos escenarios más comunes: convertir valores a booleano y números. @@ -158,7 +158,7 @@ _presencia_ del atributo indica un valor "true". Sin embargo, el `booleanAttribu `numberAttribute` intenta parsear el valor dado a un número, produciendo `NaN` si el parseo falla. -### Alias de input +### Alias de input {#input-aliases} Puedes especificar la opción `alias` para cambiar el nombre de un input en las plantillas. @@ -216,7 +216,7 @@ En otros aspectos, los model inputs funcionan de manera similar a los inputs est Consulta [Enlace bidireccional](guide/templates/two-way-binding) para más detalles sobre el enlace bidireccional en plantillas. -### Enlace bidireccional con propiedades planas +### Enlace bidireccional con propiedades planas {#two-way-binding-with-plain-properties} Puedes enlazar una propiedad JavaScript plana a un model input. @@ -234,7 +234,7 @@ export class MediaControls { En el ejemplo anterior, `CustomSlider` puede escribir valores en su model input `value`, que luego propaga esos valores de vuelta a la propiedad `volume` en `MediaControls`. Este enlace mantiene los valores de `value` y `volume` sincronizados. -### Eventos `change` implícitos +### Eventos `change` implícitos {#implicit-change-events} Cuando declaras un model input en un componente o directiva, Angular crea automáticamente un [output](guide/components/outputs) correspondiente para ese model. El nombre del output es el nombre del model input con el sufijo "Change". @@ -251,23 +251,23 @@ Angular emite este evento de cambio cada vez que escribes un nuevo valor en el m Consulta [Eventos personalizados con outputs](guide/components/outputs) para más detalles sobre outputs. -### Personalizar model inputs +### Personalizar model inputs {#customizing-model-inputs} -Puedes marcar un model input como requerido o proporcionar un alias de la misma manera que un [input estándar](guide/signals/inputs). +Puedes marcar un model input como requerido o proporcionar un alias de la misma manera que un [input estándar](guide/components/inputs). Los model inputs no admiten transformaciones de input. -### Cuándo usar model inputs +### Cuándo usar model inputs {#when-to-use-model-inputs} Usa model inputs cuando quieras que un componente admita enlace bidireccional. Esto es típicamente apropiado cuando un componente existe para modificar un valor basado en la interacción del usuario. Más comúnmente, los controles de formulario personalizados, como un selector de fecha o combobox, deben usar model inputs para su valor principal. -## Elegir nombres de input +## Elegir nombres de input {#choosing-input-names} Evita elegir nombres de input que colisionen con propiedades en elementos DOM como HTMLElement. Las colisiones de nombres introducen confusión sobre si la propiedad enlazada pertenece al componente o al elemento DOM. Evita agregar prefijos para inputs de componentes como lo harías con selectores de componentes. Dado que un elemento dado solo puede alojar un componente, se puede asumir que cualquier propiedad personalizada pertenece al componente. -## Declarar inputs con el decorador `@Input` +## Declarar inputs con el decorador `@Input` {#declaring-inputs-with-the-input-decorator} CONSEJO: Aunque el equipo de Angular recomienda usar la función `input` basada en signals para proyectos nuevos, la API original basada en decoradores `@Input` sigue siendo completamente compatible. @@ -286,11 +286,11 @@ Enlazar a un input es lo mismo tanto en inputs basados en signals como en inputs ``` -### Personalizar inputs basados en decoradores +### Personalizar inputs basados en decoradores {#customizing-decorator-based-inputs} El decorador `@Input` acepta un objeto de configuración que te permite cambiar la forma en que funciona el input. -#### Inputs requeridos +#### Inputs requeridos {#required-inputs-1} Puedes especificar la opción `required` para exigir que un input dado siempre tenga un valor. @@ -303,7 +303,7 @@ export class CustomSlider { Si intentas usar un componente sin especificar todos sus inputs requeridos, Angular reporta un error en tiempo de compilación. -#### Transformaciones de input +#### Transformaciones de input {#input-transforms-1} Puedes especificar una función `transform` para cambiar el valor de un input cuando es establecido por Angular. Esta función de transformación funciona de manera idéntica a las funciones de transformación para inputs basados en signals descritas anteriormente. @@ -321,7 +321,7 @@ function trimString(value: string | undefined) { } ``` -#### Alias de input +#### Alias de input {#input-aliases-1} Puedes especificar la opción `alias` para cambiar el nombre de un input en las plantillas. @@ -340,7 +340,7 @@ El decorador `@Input` también acepta el alias como su primer parámetro en luga Los alias de input funcionan de la misma manera que para inputs basados en signals descritos anteriormente. -### Inputs con getters y setters +### Inputs con getters y setters {#inputs-with-getters-and-setters} Cuando usas inputs basados en decoradores, una propiedad implementada con un getter y setter puede ser un input: @@ -374,7 +374,7 @@ export class CustomSlider { Evita getters y setters complejos o costosos. Angular puede invocar el setter de un input múltiples veces, lo que puede impactar negativamente el rendimiento de la aplicación si el setter realiza comportamientos costosos, como manipulación del DOM. -## Especificar inputs en el decorador `@Component` +## Especificar inputs en el decorador `@Component` {#specify-inputs-in-the-component-decorator} Además del decorador `@Input`, también puedes especificar los inputs de un componente con la propiedad `inputs` en el decorador `@Component`. Esto puede ser útil cuando un componente hereda una propiedad de una clase base: diff --git a/adev-es/src/content/guide/components/lifecycle.en.md b/adev-es/src/content/guide/components/lifecycle.en.md index 6f075427..41976533 100644 --- a/adev-es/src/content/guide/components/lifecycle.en.md +++ b/adev-es/src/content/guide/components/lifecycle.en.md @@ -256,13 +256,13 @@ next phase. ```ts import {Component, ElementRef, afterNextRender} from '@angular/core'; -@Component({...}) +@Component(/* ... */) export class UserProfile { private prevPadding = 0; private elementHeight = 0; constructor() { - private elementRef = inject(ElementRef); + const elementRef = inject(ElementRef); const nativeElement = elementRef.nativeElement; afterNextRender({ @@ -281,7 +281,7 @@ export class UserProfile { if (didWrite) { this.elementHeight = nativeElement.getBoundingClientRect().height; } - } + }, }); } } diff --git a/adev-es/src/content/guide/components/lifecycle.md b/adev-es/src/content/guide/components/lifecycle.md index eabacef4..8a918630 100644 --- a/adev-es/src/content/guide/components/lifecycle.md +++ b/adev-es/src/content/guide/components/lifecycle.md @@ -18,7 +18,7 @@ de vida descritos a continuación se ejecutan mientras Angular realiza este reco componente exactamente una vez, por lo que siempre debes evitar hacer más cambios de estado en medio del proceso. -## Resumen +## Resumen {#summary}
    Cell 1 Cell 2
    @@ -100,7 +100,7 @@ el estado del componente basándote en sus valores de input iniciales. Durante la inicialización, el primer `ngOnChanges` se ejecuta antes de `ngOnInit`. -#### Inspeccionar cambios +#### Inspeccionar cambios {#inspecting-changes} El método `ngOnChanges` acepta un argumento `SimpleChanges`. Este objeto es un [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) @@ -162,7 +162,7 @@ destruido. También puedes usar `DestroyRef` para mantener el código de configuración cerca del código de limpieza, en lugar de poner todo el código de limpieza en el método `ngOnDestroy`. -##### Detectar destrucción de instancia +##### Detectar destrucción de instancia {#detecting-instance-destruction} `DestroyRef` proporciona una propiedad `destroyed` que permite verificar si una instancia dada ya ha sido destruida. Esto es útil para evitar operaciones en componentes destruidos, especialmente cuando se trata de lógica retrasada o asíncrona. @@ -226,7 +226,7 @@ aquí, intentar cambiar cualquier estado en este método resulta en un [ExpressionChangedAfterItHasBeenCheckedError](errors/NG0100). -### afterEveryRender y afterNextRender +### afterEveryRender y afterNextRender {#aftereveryrender-and-afternextrender} Las funciones `afterEveryRender` y `afterNextRender` te permiten registrar un **callback de renderizado** para ser invocado después de que Angular ha terminado de renderizar _todos los componentes_ en la página en el DOM. @@ -244,7 +244,7 @@ Consulta [Usar APIs del DOM](guide/components/dom-apis) para orientación sobre Los callbacks de renderizado no se ejecutan durante el renderizado del lado del servidor o durante el pre-renderizado en tiempo de compilación. -#### Fases de after\*Render +#### Fases de after\*Render {#afterrender-phases} Al usar `afterEveryRender` o `afterNextRender`, opcionalmente puedes dividir el trabajo en fases. La fase te da control sobre la secuenciación de operaciones del DOM, permitiéndote secuenciar operaciones de _escritura_ @@ -296,7 +296,7 @@ Hay cuatro fases, ejecutadas en el siguiente orden: | `mixedReadWrite` | Fase predeterminada. Usa para cualquier operación que necesite tanto leer como escribir propiedades y estilos que afecten el layout. Evita esta fase si es posible, prefiriendo las fases explícitas `write` y `read`. | | `read` | Usa esta fase para leer cualquier propiedad del DOM que afecte el layout. | -## Interfaces de ciclo de vida +## Interfaces de ciclo de vida {#lifecycle-interfaces} Angular proporciona una interfaz TypeScript para cada método de ciclo de vida. Opcionalmente puedes importar e `implementar` estas interfaces para asegurar que tu implementación no tenga errores tipográficos o @@ -316,11 +316,11 @@ export class UserProfile implements OnInit { } ``` -## Orden de ejecución +## Orden de ejecución {#execution-order} Los siguientes diagramas muestran el orden de ejecución de los hooks de ciclo de vida de Angular. -### Durante la inicialización +### Durante la inicialización {#during-initialization} ```mermaid graph TD; @@ -337,7 +337,7 @@ end CHANGE--Renderizado-->afterNextRender-->afterEveryRender ``` -### Actualizaciones posteriores +### Actualizaciones posteriores {#subsequent-updates} ```mermaid graph TD; @@ -350,7 +350,7 @@ end CHANGE--Renderizado-->afterEveryRender ``` -### Orden con directivas +### Orden con directivas {#ordering-with-directives} Cuando pones una o más directivas en el mismo elemento que un componente, ya sea en una plantilla o con la propiedad `hostDirectives`, el framework no garantiza ningún orden de un hook de ciclo de vida diff --git a/adev-es/src/content/guide/components/output-function.en.md b/adev-es/src/content/guide/components/output-function.en.md deleted file mode 100644 index 77231782..00000000 --- a/adev-es/src/content/guide/components/output-function.en.md +++ /dev/null @@ -1,109 +0,0 @@ -# Function-based outputs - -The `output()` function declares an output in a directive or component. -Outputs allow you to emit values to parent components. - -HELPFUL: The `output()` function is currently in [developer preview](/reference/releases#developer-preview). - - -import {Component, output} from '@angular/core'; - -@Component({...}) -export class MyComp { - onNameChange = output() // OutputEmitterRef - - setNewName(newName: string) { - this.onNameChange.emit(newName); - } -} - - -An output is automatically recognized by Angular whenever you use the `output` function as an initializer of a class member. -Parent components can listen to outputs in templates by using the event binding syntax. - -```html - -``` - -## Aliasing an output - -Angular uses the class member name as the name of the output. -You can alias outputs to change their public name to be different. - -```typescript -class MyComp { - onNameChange = output({alias: 'ngxNameChange'}); -} -``` - -This allows users to bind to your output using `(ngxNameChange)`, while inside your component you can access the output emitter using `this.onNameChange`. - -## Subscribing programmatically - -Consumers may create your component dynamically with a reference to a `ComponentRef`. -In those cases, parents can subscribe to outputs by directly accessing the property of type `OutputRef`. - -```ts -const myComp = viewContainerRef.createComponent(...); - -myComp.instance.onNameChange.subscribe(newName => { - console.log(newName); -}); -``` - -Angular will automatically clean up the subscription when `myComp` is destroyed. -Alternatively, an object with a function to explicitly unsubscribe earlier is returned. - -## Using RxJS observables as source - -In some cases, you may want to emit output values based on RxJS observables. -Angular provides a way to use RxJS observables as source for outputs. - -The `outputFromObservable` function is a compiler primitive, similar to the `output()` function, and declares outputs that are driven by RxJS observables. - - -import {Directive} from '@angular/core'; -import {outputFromObservable} from '@angular/core/rxjs-interop'; - -@Directive(...) -class MyDir { - nameChange$ = this.dataService.get(); // Observable - nameChange = outputFromObservable(this.nameChange$); -} - - -Angular will forward subscriptions to the observable, but will stop forwarding values when the owning directive is destroyed. -In the example above, if `MyDir` is destroyed, `nameChange` will no longer emit values. - -HELPFUL: Most of the time, using `output()` is sufficient and you can emit values imperatively. - -## Converting an output to an observable - -You can subscribe to outputs by calling `.subscribe` method on `OutputRef`. -In other cases, Angular provides a helper function that converts an `OutputRef` to an observable. - - -import {outputToObservable} from '@angular/core/rxjs-interop'; - -@Component(...) -class MyComp { - onNameChange = output(); -} - -// Instance reference to `MyComp`. -const myComp: MyComp; - -outputToObservable(this.myComp.instance.onNameChange) // Observable - .pipe(...) - .subscribe(...); - - -## Why you should use `output()` over decorator-based `@Output()`? - -The `output()` function provides numerous benefits over decorator-based `@Output` and `EventEmitter`: - -1. Simpler mental model and API: -
    • No concept of error channel, completion channels, or other APIs from RxJS. -
    • Outputs are simple emitters. You can emit values using the `.emit` function. -2. More accurate types. -
    • `OutputEmitterRef.emit(value)` is now correctly typed, while `EventEmitter` has broken types and can cause runtime errors. diff --git a/adev-es/src/content/guide/components/output-function.md b/adev-es/src/content/guide/components/output-function.md deleted file mode 100644 index da940f6d..00000000 --- a/adev-es/src/content/guide/components/output-function.md +++ /dev/null @@ -1,109 +0,0 @@ -# Outputs basados en funciones - -La función `output()` declara un output en una directiva o componente. -Los outputs te permiten emitir valores a los componentes padre. - -ÚTIL: La función `output()` está actualmente en [developer preview](/reference/releases#developer-preview). - - -import {Component, output} from '@angular/core'; - -@Component({...}) -export class MyComp { - onNameChange = output() // OutputEmitterRef - - setNewName(newName: string) { - this.onNameChange.emit(newName); - } -} - - -Un output es automáticamente reconocido por Angular cada vez que usas la función `output` como inicializador de un miembro de clase. -Los componentes padre pueden escuchar outputs en las plantillas usando la sintaxis de enlace de eventos. - -```html - -``` - -## Alias de un output - -Angular usa el nombre del miembro de clase como el nombre del output. -Puedes crear un alias para los outputs para cambiar su nombre público y que sea diferente. - -```typescript -class MyComp { - onNameChange = output({alias: 'ngxNameChange'}); -} -``` - -Esto permite a los usuarios enlazar a tu output usando `(ngxNameChange)`, mientras que dentro de tu componente puedes acceder al emisor de output usando `this.onNameChange`. - -## Suscripción programática - -Los consumidores pueden crear tu componente dinámicamente con una referencia a un `ComponentRef`. -En esos casos, los padres pueden suscribirse a los outputs accediendo directamente a la propiedad de tipo `OutputRef`. - -```ts -const myComp = viewContainerRef.createComponent(...); - -myComp.instance.onNameChange.subscribe(newName => { - console.log(newName); -}); -``` - -Angular limpiará automáticamente la suscripción cuando `myComp` sea destruido. -Alternativamente, se devuelve un objeto con una función para cancelar explícitamente la suscripción antes. - -## Usando observables de RxJS como fuente - -En algunos casos, puedes querer emitir valores de output basados en observables de RxJS. -Angular proporciona una forma de usar observables de RxJS como fuente para outputs. - -La función `outputFromObservable` es una primitiva del compilador, similar a la función `output()`, y declara outputs que son impulsados por observables de RxJS. - - -import {Directive} from '@angular/core'; -import {outputFromObservable} from '@angular/core/rxjs-interop'; - -@Directive(...) -class MyDir { - nameChange$ = this.dataService.get(); // Observable - nameChange = outputFromObservable(this.nameChange$); -} - - -Angular reenviará las suscripciones al observable, pero dejará de reenviar valores cuando la directiva propietaria sea destruida. -En el ejemplo anterior, si `MyDir` es destruida, `nameChange` dejará de emitir valores. - -ÚTIL: La mayoría de las veces, usar `output()` es suficiente y puedes emitir valores imperativamente. - -## Convirtiendo un output a un observable - -Puedes suscribirte a outputs llamando al método `.subscribe` en `OutputRef`. -En otros casos, Angular proporciona una función auxiliar que convierte un `OutputRef` a un observable. - - -import {outputToObservable} from '@angular/core/rxjs-interop'; - -@Component(...) -class MyComp { - onNameChange = output(); -} - -// Referencia de instancia a `MyComp`. -const myComp: MyComp; - -outputToObservable(this.myComp.instance.onNameChange) // Observable - .pipe(...) - .subscribe(...); - - -## ¿Por qué deberías usar `output()` sobre `@Output()` basado en decoradores? - -La función `output()` proporciona numerosos beneficios sobre `@Output` y `EventEmitter` basados en decoradores: - -1. Modelo mental y API más simples: -
    • Sin concepto de canal de error, canales de completado, u otras APIs de RxJS. -
    • Los outputs son simples emisores. Puedes emitir valores usando la función `.emit`. -2. Tipos más precisos. -
    • `OutputEmitterRef.emit(value)` ahora está correctamente tipado, mientras que `EventEmitter` tiene tipos rotos y puede causar errores en tiempo de ejecución. diff --git a/adev-es/src/content/guide/components/outputs.en.md b/adev-es/src/content/guide/components/outputs.en.md index 57a302cc..e61cfed5 100644 --- a/adev-es/src/content/guide/components/outputs.en.md +++ b/adev-es/src/content/guide/components/outputs.en.md @@ -4,8 +4,10 @@ TIP: This guide assumes you've already read the [Essentials Guide](essentials). Angular components can define custom events by assigning a property to the `output` function: -```ts {highlight:[3]} -@Component({/*...*/}) +```ts {highlight:[5]} +@Component({ + /*...*/ +}) export class ExpandablePanel { panelClosed = output(); } @@ -18,7 +20,7 @@ export class ExpandablePanel { The `output` function returns an `OutputEmitterRef`. You can emit an event by calling the `emit` method on the `OutputEmitterRef`: ```ts - this.panelClosed.emit(); +this.panelClosed.emit(); ``` Angular refers to properties initialized with the `output` function as **outputs**. You can use outputs to raise custom events, similar to native browser events like `click`. @@ -43,7 +45,7 @@ this.valueChanged.emit(7); this.thumbDropped.emit({ pointerX: 123, pointerY: 456, -}) +}); ``` When defining an event listener in a template, you can access the event data from the `$event` variable: @@ -71,7 +73,7 @@ export class App { The `output` function accepts a parameter that lets you specify a different name for the event in a template: ```ts -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { changed = output({alias: 'valueChanged'}); } @@ -93,7 +95,7 @@ from the component instance. The `OutputRef` type includes a `subscribe` method: ```ts const someComponentRef: ComponentRef = viewContainerRef.createComponent(/*...*/); -someComponentRef.instance.someEventProperty.subscribe(eventData => { +someComponentRef.instance.someEventProperty.subscribe((eventData) => { console.log(eventData); }); ``` @@ -101,7 +103,7 @@ someComponentRef.instance.someEventProperty.subscribe(eventData => { Angular automatically cleans up event subscriptions when it destroys components with subscribers. Alternatively, you can manually unsubscribe from an event. The `subscribe` function returns an `OutputRefSubscription` with an `unsubscribe` method: ```ts -const eventSubscription = someComponent.someEventProperty.subscribe(eventData => { +const eventSubscription = someComponent.someEventProperty.subscribe((eventData) => { console.log(eventData); }); @@ -130,7 +132,7 @@ original decorator-based `@Output` API remains fully supported. You can alternatively define custom events by assigning a property to a new `EventEmitter` and adding the `@Output` decorator: ```ts -@Component({/*...*/}) +@Component(/* ... */) export class ExpandablePanel { @Output() panelClosed = new EventEmitter(); } @@ -143,7 +145,7 @@ You can emit an event by calling the `emit` method on the `EventEmitter`. The `@Output` decorator accepts a parameter that lets you specify a different name for the event in a template: ```ts -@Component({/*...*/}) +@Component(/* ... */) export class CustomSlider { @Output('valueChanged') changed = new EventEmitter(); } diff --git a/adev-es/src/content/guide/components/outputs.md b/adev-es/src/content/guide/components/outputs.md index bd666ce1..71749c18 100644 --- a/adev-es/src/content/guide/components/outputs.md +++ b/adev-es/src/content/guide/components/outputs.md @@ -31,7 +31,7 @@ Al extender una clase de componente, **los outputs son heredados por la clase hi La función `output` tiene un significado especial para el compilador de Angular. **Solo puedes llamar a `output` en inicializadores de propiedades de componentes y directivas.** -## Emitir datos del evento +## Emitir datos del evento {#emitting-event-data} Puedes pasar datos del evento al llamar a `emit`: @@ -66,7 +66,7 @@ export class App { ``` -## Personalizar nombres de output +## Personalizar nombres de output {#customizing-output-names} La función `output` acepta un parámetro que te permite especificar un nombre diferente para el evento en una plantilla: @@ -85,7 +85,7 @@ Este alias no afecta el uso de la propiedad en código TypeScript. Aunque generalmente debes evitar usar alias para outputs de componentes, esta característica puede ser útil para renombrar propiedades mientras se preserva un alias para el nombre original o para evitar colisiones con el nombre de eventos DOM nativos. -## Suscribirse a outputs programáticamente +## Suscribirse a outputs programáticamente {#subscribing-to-outputs-programmatically} Al crear un componente dinámicamente, puedes suscribirte programáticamente a eventos de output desde la instancia del componente. El tipo `OutputRef` incluye un método `subscribe`: @@ -110,7 +110,7 @@ const eventSubscription = someComponent.someEventProperty.subscribe(eventData => eventSubscription.unsubscribe(); ``` -## Elegir nombres de eventos +## Elegir nombres de eventos {#choosing-event-names} Evita elegir nombres de output que colisionen con eventos en elementos DOM como HTMLElement. Las colisiones de nombres introducen confusión sobre si la propiedad enlazada pertenece al componente o al elemento DOM. @@ -118,11 +118,11 @@ Evita agregar prefijos para outputs de componentes como lo harías con selectore Siempre usa [camelCase](https://en.wikipedia.org/wiki/Camel_case) para nombres de output. Evita prefijar nombres de output con "on". -## Usar outputs con RxJS +## Usar outputs con RxJS {#using-outputs-with-rxjs} Consulta [Interoperabilidad de RxJS con outputs de componentes y directivas](ecosystem/rxjs-interop/output-interop) para detalles sobre la interoperabilidad entre outputs y RxJS. -## Declarar outputs con el decorador `@Output` +## Declarar outputs con el decorador `@Output` {#declaring-outputs-with-the-output-decorator} CONSEJO: Aunque el equipo de Angular recomienda usar la función `output` para proyectos nuevos, la API original basada en decoradores `@Output` sigue siendo completamente compatible. @@ -138,7 +138,7 @@ export class ExpandablePanel { Puedes emitir un evento llamando al método `emit` en el `EventEmitter`. -### Alias con el decorador `@Output` +### Alias con el decorador `@Output` {#aliases-with-the-output-decorator} El decorador `@Output` acepta un parámetro que te permite especificar un nombre diferente para el evento en una plantilla: @@ -155,7 +155,7 @@ export class CustomSlider { Este alias no afecta el uso de la propiedad en código TypeScript. -## Especificar outputs en el decorador `@Component` +## Especificar outputs en el decorador `@Component` {#specify-outputs-in-the-component-decorator} Además del decorador `@Output`, también puedes especificar los outputs de un componente con la propiedad `outputs` en el decorador `@Component`. Esto puede ser útil cuando un componente hereda una propiedad de una clase base: diff --git a/adev-es/src/content/guide/components/programmatic-rendering.en.md b/adev-es/src/content/guide/components/programmatic-rendering.en.md index e604e736..39929912 100644 --- a/adev-es/src/content/guide/components/programmatic-rendering.en.md +++ b/adev-es/src/content/guide/components/programmatic-rendering.en.md @@ -20,10 +20,10 @@ chunks automatically and loaded only when necessary, based on the configured tri template. ```angular-ts -@Component({ ... }) +@Component({/*...*/}) export class AdminBio { /* ... */ } -@Component({ ... }) +@Component({/*...*/}) export class StandardBio { /* ... */ } @Component({ @@ -41,6 +41,144 @@ export class CustomDialog { } ``` +### Passing inputs to dynamically rendered components + +You can pass inputs to the dynamically rendered component using the `ngComponentOutletInputs` property. This property accepts an object where keys are input names and values are the input values. + +```angular-ts +@Component({ + selector: 'user-greeting', + template: ` +
    +

    User: {{ username() }}

    +

    Role: {{ role() }}

    +
    + `, +}) +export class UserGreeting { + username = input.required(); + role = input('guest'); +} + +@Component({ + selector: 'profile-view', + imports: [NgComponentOutlet], + template: ``, +}) +export class ProfileView { + greetingComponent = UserGreeting; + greetingInputs = signal({username: 'ngAwesome', role: 'admin'}); +} +``` + +The inputs are updated whenever the `greetingInputs` signal changes, keeping the dynamic component in sync with the parent's state. + +### Providing content projection + +Use `ngComponentOutletContent` to pass projected content to the dynamically rendered component. This is useful when the dynamic component uses `` to display content. + +```angular-ts +@Component({ + selector: 'card-wrapper', + template: ` +
    + +
    + `, +}) +export class CardWrapper {} + +@Component({ + imports: [NgComponentOutlet], + template: ` + + + +

    Dynamic Content

    +

    This content is projected into the card.

    +
    + `, +}) +export class DynamicCard { + private vcr = inject(ViewContainerRef); + cardComponent = CardWrapper; + + private contentTemplate = viewChild>('contentTemplate'); + + cardContent = computed(() => { + const template = this.contentTemplate(); + if (!template) return []; + // Returns an array of projection slots. Each element represents one slot. + // CardWrapper has one , so we return an array with one element. + return [this.vcr.createEmbeddedView(template).rootNodes]; + }); +} +``` + +NOTE: Hydration does not support projecting DOM nodes created with native DOM APIs. This causes an [NG0503 error](/errors/NG0503). Use Angular APIs to create projected content or add `ngSkipHydration` to the component. + +### Providing injectors + +You can provide a custom injector to the dynamically created component using `ngComponentOutletInjector`. This is useful for providing component-specific services or configuration. + +```angular-ts +export const THEME_DATA = new InjectionToken('THEME_DATA', { + factory: () => 'light', +}); + +@Component({ + selector: 'themed-panel', + template: `
    ...
    `, +}) +export class ThemedPanel { + theme = inject(THEME_DATA); +} + +@Component({ + selector: 'dynamic-panel', + imports: [NgComponentOutlet], + template: ``, +}) +export class DynamicPanel { + panelComponent = ThemedPanel; + + customInjector = Injector.create({ + providers: [{provide: THEME_DATA, useValue: 'dark'}], + }); +} +``` + +### Accessing the component instance + +You can access the dynamically created component's instance using the directive's `exportAs` feature: + +```angular-ts +@Component({ + selector: 'counter', + template: `

    Count: {{ count() }}

    `, +}) +export class Counter { + count = signal(0); + increment() { + this.count.update((c) => c + 1); + } +} + +@Component({ + imports: [NgComponentOutlet], + template: ` + + + + `, +}) +export class CounterHost { + counterComponent = Counter; +} +``` + +NOTE: The `componentInstance` property is `null` before the component is rendered. + See the [NgComponentOutlet API reference](api/common/NgComponentOutlet) for more information on the directive's capabilities. @@ -57,9 +195,7 @@ DOM as the next sibling of the component or directive that injected the `ViewCon ```angular-ts @Component({ selector: 'leaf-content', - template: ` - This is the leaf content - `, + template: `This is the leaf content`, }) export class LeafContent {} @@ -75,9 +211,7 @@ export class OuterContainer {} @Component({ selector: 'inner-item', - template: ` - - `, + template: ``, }) export class InnerItem { private viewContainer = inject(ViewContainerRef); @@ -148,25 +282,25 @@ To simplify this, both `createComponent` and `ViewContainerRef.createComponent` ### Host view using `ViewContainerRef.createComponent` -`ViewContainerRef.createComponent` creates a component and automatically inserts its host view and host element into the container's view hierarchy at the container's location. Use this when the dynamic component should become part of the container's logical and visual structure (for example, adding list items or inline UI). +`ViewContainerRef.createComponent` creates a component and automatically inserts its host view and host element into the container’s view hierarchy at the container’s location. Use this when the dynamic component should become part of the container’s logical and visual structure (for example, adding list items or inline UI). -By contrast, the standalone `createComponent` API does not attach the new component to any existing view or DOM location — it returns a `ComponentRef` and gives you explicit control over where to place the component's host element. +By contrast, the standalone `createComponent` API does not attach the new component to any existing view or DOM location — it returns a `ComponentRef` and gives you explicit control over where to place the component’s host element. ```angular-ts -import { Component, input, model, output } from "@angular/core"; +import {Component, input, model, output} from '@angular/core'; @Component({ selector: 'app-warning', template: ` - @if(isExpanded()) { -
    -

    Warning: Action needed!

    - -
    - } - ` + @if (isExpanded()) { +
    +

    Warning: Action needed!

    + +
    + } + `, }) -export class AppWarningComponent { +export class AppWarning { readonly canClose = input.required(); readonly isExpanded = model(); readonly close = output(); @@ -174,41 +308,49 @@ export class AppWarningComponent { ``` ```ts -import { Component, ViewContainerRef, signal, inputBinding, outputBinding, twoWayBinding, inject } from '@angular/core'; -import { FocusTrap } from "@angular/cdk/a11y"; -import { ThemeDirective } from '../theme.directive'; +import { + Component, + ViewContainerRef, + signal, + inputBinding, + outputBinding, + twoWayBinding, + inject, +} from '@angular/core'; +import {FocusTrap} from '@angular/cdk/a11y'; +import {ThemeDirective} from '../theme.directive'; @Component({ - template: `` + template: ``, }) -export class HostComponent { +export class Host { private vcr = inject(ViewContainerRef); readonly canClose = signal(true); readonly isExpanded = signal(true); showWarning() { - const compRef = this.vcr.createComponent(AppWarningComponent, { + const compRef = this.vcr.createComponent(AppWarning, { bindings: [ inputBinding('canClose', this.canClose), twoWayBinding('isExpanded', this.isExpanded), outputBinding('close', (confirmed) => { console.log('Closed with result:', confirmed); - }) + }), ], directives: [ FocusTrap, - { type: ThemeDirective, bindings: [inputBinding('theme', () => 'warning')] } - ] + {type: ThemeDirective, bindings: [inputBinding('theme', () => 'warning')]}, + ], }); } } ``` -In the example above, the dynamic **AppWarningComponent** is created with its `canClose` input bound to a reactive signal, a two-way binding on its `isExpanded` state, and an output listener for `close`. The `FocusTrap` and `ThemeDirective` are attached to the host element via `directives`. +In the example above, the dynamic **AppWarning** is created with its `canClose` input bound to a reactive signal, a two-way binding on its `isExpanded` state, and an output listener for `close`. The `FocusTrap` and `ThemeDirective` are attached to the host element via `directives`. ### Popup attached to `document.body` with `createComponent` + `hostElement` -Use this when rendering outside the current view hierarchy (e.g., overlays). The provided `hostElement` becomes the component's host in the DOM, so Angular doesn't create a new element matching the selector. Lets you configure **bindings** directly. +Use this when rendering outside the current view hierarchy (e.g., overlays). The provided `hostElement` becomes the component’s host in the DOM, so Angular doesn’t create a new element matching the selector. Lets you configure **bindings** directly. ```ts import { @@ -219,10 +361,11 @@ import { Injectable, inputBinding, outputBinding, + Service, } from '@angular/core'; -import { PopupComponent } from './popup.component'; +import {Popup} from './popup'; -@Injectable({ providedIn: 'root' }) +@Service() export class PopupService { private readonly injector = inject(EnvironmentInjector); private readonly appRef = inject(ApplicationRef); @@ -232,7 +375,7 @@ export class PopupService { const host = document.createElement('popup-host'); // Create the component and bind in one call - const ref = createComponent(PopupComponent, { + const ref = createComponent(Popup, { environmentInjector: this.injector, hostElement: host, bindings: [ @@ -245,7 +388,7 @@ export class PopupService { ], }); - // Registers the component's view so it participates in change detection cycle. + // Registers the component’s view so it participates in change detection cycle. this.appRef.attachView(ref.hostView); // Inserts the provided host element into the DOM (outside the normal Angular view hierarchy). // This is what makes the popup visible on screen, typically used for overlays or modals. diff --git a/adev-es/src/content/guide/components/programmatic-rendering.md b/adev-es/src/content/guide/components/programmatic-rendering.md index 5e85df99..04e5b932 100644 --- a/adev-es/src/content/guide/components/programmatic-rendering.md +++ b/adev-es/src/content/guide/components/programmatic-rendering.md @@ -14,16 +14,16 @@ usar la funcionalidad incorporada [`@defer`](/guide/templates/defer) en su lugar de cualquier componente, directiva y pipe dentro del bloque `@defer` sea extraído en chunks de JavaScript separados automáticamente y cargados solo cuando sea necesario, basado en los triggers configurados. -## Usando NgComponentOutlet +## Usando NgComponentOutlet {#using-ngcomponentoutlet} `NgComponentOutlet` es una directiva estructural que renderiza dinámicamente un componente dado en una plantilla. ```angular-ts -@Component({ ... }) +@Component({/*...*/}) export class AdminBio { /* ... */ } -@Component({ ... }) +@Component({/*...*/}) export class StandardBio { /* ... */ } @Component({ @@ -44,7 +44,145 @@ export class CustomDialog { Ve la [referencia de API de NgComponentOutlet](api/common/NgComponentOutlet) para más información sobre las capacidades de la directiva. -## Usando ViewContainerRef +### Pasar entradas a componentes renderizados dinámicamente {#passing-inputs-to-dynamically-rendered-components} + +Puedes pasar entradas al componente renderizado dinámicamente usando la propiedad `ngComponentOutletInputs`. Esta propiedad acepta un objeto donde las claves son nombres de entradas y los valores son los valores de las entradas. + +```angular-ts +@Component({ + selector: 'user-greeting', + template: ` +
    +

    User: {{ username() }}

    +

    Role: {{ role() }}

    +
    + `, +}) +export class UserGreeting { + username = input.required(); + role = input('guest'); +} + +@Component({ + selector: 'profile-view', + imports: [NgComponentOutlet], + template: ``, +}) +export class ProfileView { + greetingComponent = UserGreeting; + greetingInputs = signal({username: 'ngAwesome', role: 'admin'}); +} +``` + +Las entradas se actualizan cada vez que la signal `greetingInputs` cambia, manteniendo el componente dinámico sincronizado con el estado del padre. + +### Proporcionar proyección de contenido {#providing-content-projection} + +Usa `ngComponentOutletContent` para pasar contenido proyectado al componente renderizado dinámicamente. Esto es útil cuando el componente dinámico usa `` para mostrar contenido. + +```angular-ts +@Component({ + selector: 'card-wrapper', + template: ` +
    + +
    + `, +}) +export class CardWrapper {} + +@Component({ + imports: [NgComponentOutlet], + template: ` + + + +

    Dynamic Content

    +

    This content is projected into the card.

    +
    + `, +}) +export class DynamicCard { + private vcr = inject(ViewContainerRef); + cardComponent = CardWrapper; + + private contentTemplate = viewChild>('contentTemplate'); + + cardContent = computed(() => { + const template = this.contentTemplate(); + if (!template) return []; + // Devuelve un array de slots de proyección. Cada elemento representa un slot . + // CardWrapper tiene un , así que devolvemos un array con un elemento. + return [this.vcr.createEmbeddedView(template).rootNodes]; + }); +} +``` + +NOTA: La hidratación no admite la proyección de nodos DOM creados con APIs nativas del DOM. Esto causa un [error NG0503](/errors/NG0503). Usa APIs de Angular para crear contenido proyectado o agrega `ngSkipHydration` al componente. + +### Proporcionar inyectores {#providing-injectors} + +Puedes proporcionar un inyector personalizado al componente creado dinámicamente usando `ngComponentOutletInjector`. Esto es útil para proporcionar servicios o configuración específicos del componente. + +```angular-ts +export const THEME_DATA = new InjectionToken('THEME_DATA', { + factory: () => 'light', +}); + +@Component({ + selector: 'themed-panel', + template: `
    ...
    `, +}) +export class ThemedPanel { + theme = inject(THEME_DATA); +} + +@Component({ + selector: 'dynamic-panel', + imports: [NgComponentOutlet], + template: ``, +}) +export class DynamicPanel { + panelComponent = ThemedPanel; + + customInjector = Injector.create({ + providers: [{provide: THEME_DATA, useValue: 'dark'}], + }); +} +``` + +### Acceder a la instancia del componente {#accessing-the-component-instance} + +Puedes acceder a la instancia del componente creado dinámicamente usando la característica `exportAs` de la directiva: + +```angular-ts +@Component({ + selector: 'counter', + template: `

    Count: {{ count() }}

    `, +}) +export class Counter { + count = signal(0); + increment() { + this.count.update((c) => c + 1); + } +} + +@Component({ + imports: [NgComponentOutlet], + template: ` + + + + `, +}) +export class CounterHost { + counterComponent = Counter; +} +``` + +NOTA: La propiedad `componentInstance` es `null` antes de que el componente sea renderizado. + +## Usando ViewContainerRef {#using-viewcontainerref} Un **contenedor de vista** es un nodo en el árbol de componentes de Angular que puede contener contenido. Cualquier componente o directiva puede inyectar `ViewContainerRef` para obtener una referencia a un contenedor de vista correspondiente a @@ -101,7 +239,7 @@ En el ejemplo anterior, hacer clic en el botón "Load content" resulta en la sig ``` -## Carga diferida de componentes +## Carga diferida de componentes {#lazy-loading-components} ÚTIL: si quieres cargar de forma diferida algunos componentes, puedes considerar usar la funcionalidad incorporada [`@defer`](/guide/templates/defer) en su lugar. @@ -140,13 +278,13 @@ export class AdminSettings { El ejemplo anterior carga y muestra el `AdvancedSettings` al recibir un clic en el botón. -## Enlazando inputs, outputs y estableciendo directivas host en la creación +## Enlazando inputs, outputs y estableciendo directivas host en la creación {#binding-inputs-outputs-and-setting-host-directives-at-creation} Cuando creas componentes dinámicamente, establecer inputs manualmente y suscribirse a outputs puede ser propenso a errores. A menudo necesitas escribir código extra solo para conectar los enlaces después de que el componente es instanciado. Para simplificar esto, tanto `createComponent` como `ViewContainerRef.createComponent` soportan pasar un array `bindings` con helpers como `inputBinding()`, `outputBinding()` y `twoWayBinding()` para configurar inputs y outputs de antemano. También puedes especificar un array `directives` para aplicar cualquier directiva host. Esto permite crear componentes programáticamente con enlaces similares a plantillas en una sola llamada declarativa. -### Vista host usando `ViewContainerRef.createComponent` +### Vista host usando `ViewContainerRef.createComponent` {#host-view-using-viewcontainerrefcreatecomponent} `ViewContainerRef.createComponent` crea un componente e inserta automáticamente su vista host y elemento host en la jerarquía de vistas del contenedor en la ubicación del contenedor. Usa esto cuando el componente dinámico debe convertirse en parte de la estructura lógica y visual del contenedor (por ejemplo, añadiendo elementos de lista o UI en línea). @@ -206,7 +344,7 @@ export class HostComponent { En el ejemplo anterior, el **AppWarningComponent** dinámico es creado con su input `canClose` enlazado a un signal reactivo, un enlace bidireccional en su estado `isExpanded`, y un listener de output para `close`. El `FocusTrap` y `ThemeDirective` están adjuntos al elemento host a través de `directives`. -### Popup adjunto a `document.body` con `createComponent` + `hostElement` +### Popup adjunto a `document.body` con `createComponent` + `hostElement` {#popup-attached-to-documentbody-with-createcomponent--hostelement} Usa esto cuando renderizas fuera de la jerarquía de vistas actual (por ejemplo, overlays). El `hostElement` proporcionado se convierte en el host del componente en el DOM, por lo que Angular no crea un nuevo elemento que coincida con el selector. Te permite configurar **bindings** directamente. @@ -219,10 +357,11 @@ import { Injectable, inputBinding, outputBinding, + Service, } from '@angular/core'; -import { PopupComponent } from './popup.component'; +import {Popup} from './popup'; -@Injectable({ providedIn: 'root' }) +@Service() export class PopupService { private readonly injector = inject(EnvironmentInjector); private readonly appRef = inject(ApplicationRef); @@ -232,7 +371,7 @@ export class PopupService { const host = document.createElement('popup-host'); // Crear el componente y enlazar en una sola llamada - const ref = createComponent(PopupComponent, { + const ref = createComponent(Popup, { environmentInjector: this.injector, hostElement: host, bindings: [ diff --git a/adev-es/src/content/guide/components/queries.en.md b/adev-es/src/content/guide/components/queries.en.md index 075d2c73..8ce4e253 100644 --- a/adev-es/src/content/guide/components/queries.en.md +++ b/adev-es/src/content/guide/components/queries.en.md @@ -7,7 +7,7 @@ A component can define **queries** that find child elements and read values from Developers most commonly use queries to retrieve references to child components, directives, DOM elements, and more. All query functions return signals that reflect the most up-to-date results. You can read the -result by calling the signal function, including in reactive contexts like `computed` and `effect`. +result by calling the signal function, including in [reactive contexts](guide/signals#reactive-contexts) like `computed` and `effect`. There are two categories of query: **view queries** and **content queries.** @@ -15,7 +15,7 @@ There are two categories of query: **view queries** and **content queries.** View queries retrieve results from the elements in the component's _view_ — the elements defined in the component's own template. You can query for a single result with the `viewChild` function. -```typescript {highlight: [14, 15]} +```angular-ts {highlight: [14, 15]} @Component({ selector: 'custom-card-header', /*...*/ @@ -40,7 +40,7 @@ If the query does not find a result, its value is `undefined`. This may occur if You can also query for multiple results with the `viewChildren` function. -```typescript {highlight: [17]} +```angular-ts {highlight: [17]} @Component({ selector: 'custom-card-action', /*...*/ @@ -51,13 +51,14 @@ export class CustomCardAction { @Component({ selector: 'custom-card', - template: `Save + template: ` + Save Cancel `, }) export class CustomCard { actions = viewChildren(CustomCardAction); - actionsTexts = computed(() => this.actions().map(action => action.text); + actionsTexts = computed(() => this.actions().map((action) => action.text)); } ``` @@ -69,7 +70,7 @@ export class CustomCard { Content queries retrieve results from the elements in the component's _content_— the elements nested inside the component in the template where it's used. You can query for a single result with the `contentChild` function. -```typescript {highlight: [14, 15]} +```angular-ts {highlight: [14, 15]} @Component({ selector: 'custom-toggle', /*...*/ @@ -88,16 +89,15 @@ export class CustomExpando { } @Component({ -/* ... */ -// CustomToggle is used inside CustomExpando as content. -template: ` + /* ... */ + // CustomToggle is used inside CustomExpando as content. + template: ` Show - ` + `, }) - -export class UserProfile { } +export class UserProfile {} ``` If the query does not find a result, its value is `undefined`. This may occur if the target element is absent or hidden by `@if`. Angular keeps the result of `contentChild` up to date as your application state changes. @@ -106,7 +106,7 @@ By default, content queries find only _direct_ children of the component and do You can also query for multiple results with the `contentChildren` function. -```typescript {highlight: [14, 16, 17, 18, 19, 20]} +```angular-ts {highlight: [14, 15]} @Component({ selector: 'custom-menu-item', /*...*/ @@ -119,10 +119,9 @@ export class CustomMenuItem { selector: 'custom-menu', /*...*/ }) - export class CustomMenu { items = contentChildren(CustomMenuItem); - itemTexts = computed(() => this.items().map(item => item.text)); + itemTexts = computed(() => this.items().map((item) => item.text)); } @Component({ @@ -132,9 +131,9 @@ export class CustomMenu { Cheese Tomato - ` + `, }) -export class UserProfile { } +export class UserProfile {} ``` `contentChildren` creates a signal with an `Array` of the query results. @@ -147,8 +146,8 @@ If a child query (`viewChild` or `contentChild`) does not find a result, its val In some cases, especially with `viewChild`, you know with certainty that a specific child is always available. In other cases, you may want to strictly enforce that a specific child is present. For these cases, you can use a _required query_. -```angular-ts -@Component({/* ... */}) +```ts +@Component(/* ... */) export class CustomCard { header = viewChild.required(CustomCardHeader); body = contentChild.required(CustomCardBody); @@ -172,7 +171,7 @@ a [template reference variable](guide/templates/variables#template-reference-var template: ` - ` + `, }) export class ActionBar { saveButton = viewChild>('save'); @@ -196,9 +195,9 @@ const SUB_ITEM = new InjectionToken('sub-item'); /*...*/ providers: [{provide: SUB_ITEM, useValue: 'special-item'}], }) -export class SpecialItem { } +export class SpecialItem {} -@Component({/*...*/}) +@Component(/* ... */) export class CustomList { subItemType = contentChild(SUB_ITEM); } @@ -215,8 +214,7 @@ All query functions accept an options object as a second parameter. These option By default, the query locator indicates both the element you're searching for and the value retrieved. You can alternatively specify the `read` option to retrieve a different value from the element matched by the locator. ```ts - -@Component({/*...*/}) +@Component(/* ... */) export class CustomExpando { toggle = contentChild(ExpandoContent, {read: TemplateRef}); } @@ -232,7 +230,7 @@ Developers most commonly use `read` to retrieve `ElementRef` and `TemplateRef`. By default, `contentChildren` queries find only _direct_ children of the component and do not traverse into descendants. `contentChild` queries do traverse into descendants by default. -```typescript {highlight: [13, 14, 15, 16]} +```angular-ts {highlight: [13, 14, 15, 16, 17]} @Component({ selector: 'custom-expando', /*...*/ @@ -244,14 +242,15 @@ export class CustomExpando { @Component({ selector: 'user-profile', - template: ` + template: ` + Show - ` + `, }) -export class UserProfile { } +export class UserProfile {} ``` In the example above, `CustomExpando` cannot find `` with `contentChildren` because it is not a direct child of ``. By setting `descendants: true`, you configure the query to traverse all descendants in the same template. Queries, however, _never_ pierce into components to traverse elements in other templates. @@ -265,11 +264,11 @@ original decorator-based query APIs remain fully supported. You can alternatively declare queries by adding the corresponding decorator to a property. Decorator-based queries behave the same way as signal-based queries except as described below. -### View queries +### View queries {#decorator-view-queries} You can query for a single result with the `@ViewChild` decorator. -```typescript {highlight: [14, 16, 17, 18]} +```angular-ts {highlight: [14, 16, 17, 18]} @Component({ selector: 'custom-card-header', /*...*/ @@ -282,7 +281,7 @@ export class CustomCardHeader { selector: 'custom-card', template: 'Visit sunny California!', }) -export class CustomCard { +export class CustomCard implements AfterViewInit { @ViewChild(CustomCardHeader) header: CustomCardHeader; ngAfterViewInit() { @@ -299,7 +298,7 @@ Angular keeps the result of `@ViewChild` up to date as your application state ch You can also query for multiple results with the `@ViewChildren` decorator. -```typescript {highlight: [17, 19, 20, 21, 22, 23]} +```angular-ts {highlight: [17, 19, 20, 21, 22, 23]} @Component({ selector: 'custom-card-action', /*...*/ @@ -315,11 +314,11 @@ export class CustomCardAction { Cancel `, }) -export class CustomCard { +export class CustomCard implements AfterViewInit { @ViewChildren(CustomCardAction) actions: QueryList; ngAfterViewInit() { - this.actions.forEach(action => { + this.actions.forEach((action) => { console.log(action.text); }); } @@ -328,11 +327,11 @@ export class CustomCard { `@ViewChildren` creates a `QueryList` object that contains the query results. You can subscribe to changes to the query results over time via the `changes` property. -### Content queries +### Content queries {#decorator-content-queries} You can query for a single result with the `@ContentChild` decorator. -```typescript {highlight: [14, 16, 17, 18, 25]} +```angular-ts {highlight: [14, 16, 17, 18]} @Component({ selector: 'custom-toggle', /*...*/ @@ -345,8 +344,7 @@ export class CustomToggle { selector: 'custom-expando', /*...*/ }) - -export class CustomExpando { +export class CustomExpando implements AfterContentInit { @ContentChild(CustomToggle) toggle: CustomToggle; ngAfterContentInit() { @@ -360,9 +358,9 @@ export class CustomExpando { Show - ` + `, }) -export class UserProfile { } +export class UserProfile {} ``` In this example, the `CustomExpando` component queries for a child `CustomToggle` and accesses the result in `ngAfterContentInit`. @@ -373,7 +371,7 @@ Angular keeps the result of `@ContentChild` up to date as your application state You can also query for multiple results with the `@ContentChildren` decorator. -```typescript {highlight: [15, 17, 18, 19, 20, 21]} +```angular-ts {highlight: [14, 16, 17, 18, 19, 20]} @Component({ selector: 'custom-menu-item', /*...*/ @@ -386,12 +384,11 @@ export class CustomMenuItem { selector: 'custom-menu', /*...*/ }) - -export class CustomMenu { +export class CustomMenu implements AfterContentInit { @ContentChildren(CustomMenuItem) items: QueryList; ngAfterContentInit() { - this.items.forEach(item => { + this.items.forEach((item) => { console.log(item.text); }); } @@ -404,9 +401,9 @@ export class CustomMenu { Cheese Tomato - ` + `, }) -export class UserProfile { } +export class UserProfile {} ``` `@ContentChildren` creates a `QueryList` object that contains the query results. You can subscribe to changes to the query results over time via the `changes` property. @@ -424,7 +421,7 @@ All query decorators accept an options object as a second parameter. These optio selector: 'custom-card', template: 'Visit sunny California!', }) -export class CustomCard { +export class CustomCard implements OnInit { @ViewChild(CustomCardHeader, {static: true}) header: CustomCardHeader; ngOnInit() { diff --git a/adev-es/src/content/guide/components/queries.md b/adev-es/src/content/guide/components/queries.md index 8e8578a5..50492984 100644 --- a/adev-es/src/content/guide/components/queries.md +++ b/adev-es/src/content/guide/components/queries.md @@ -11,7 +11,7 @@ resultado llamando a la función signal, incluyendo en contextos reactivos como Hay dos categorías de consulta: **consultas de vista** y **consultas de contenido.** -## Consultas de vista +## Consultas de vista {#view-queries} Las consultas de vista recuperan resultados de los elementos en la _vista_ del componente — los elementos definidos en la propia plantilla del componente. Puedes consultar por un solo resultado con la función `viewChild`. @@ -65,7 +65,7 @@ export class CustomCard { **Las consultas nunca atraviesan los límites de los componentes.** Las consultas de vista solo pueden recuperar resultados de la plantilla del componente. -## Consultas de contenido +## Consultas de contenido {#content-queries} Las consultas de contenido recuperan resultados de los elementos en el _contenido_ del componente — los elementos anidados dentro del componente en la plantilla donde se usa. Puedes consultar por un solo resultado con la función `contentChild`. @@ -141,7 +141,7 @@ export class UserProfile { } **Las consultas nunca atraviesan los límites de los componentes.** Las consultas de contenido solo pueden recuperar resultados de la misma plantilla que el componente mismo. -## Consultas requeridas +## Consultas requeridas {#required-queries} Si una consulta de hijo (`viewChild` o `contentChild`) no encuentra un resultado, su valor es `undefined`. Esto puede ocurrir si el elemento objetivo está oculto por una declaración de flujo de control como `@if` o `@for`. Debido a esto, las consultas de hijo devuelven un signal que incluye `undefined` en su tipo de valor. @@ -157,7 +157,7 @@ export class CustomCard { Si una consulta requerida no encuentra un resultado coincidente, Angular reporta un error. Debido a que esto garantiza que un resultado está disponible, las consultas requeridas no incluyen automáticamente `undefined` en el tipo de valor del signal. -## Localizadores de consulta +## Localizadores de consulta {#query-locators} El primer parámetro para cada decorador de consulta es su **localizador**. @@ -183,7 +183,7 @@ Si más de un elemento define la misma variable de referencia de plantilla, la c Angular no soporta selectores CSS como localizadores de consulta. -### Consultas y el árbol de inyectores +### Consultas y el árbol de inyectores {#queries-and-the-injector-tree} CONSEJO: Ve [Inyección de Dependencias](guide/di) para información de fondo sobre providers y el árbol de inyección de Angular. @@ -206,11 +206,11 @@ export class CustomList { El ejemplo anterior usa un `InjectionToken` como localizador, pero puedes usar cualquier `ProviderToken` para localizar elementos específicos. -## Opciones de consulta +## Opciones de consulta {#query-options} Todas las funciones de consulta aceptan un objeto de opciones como segundo parámetro. Estas opciones controlan cómo la consulta encuentra sus resultados. -### Leyendo valores específicos del inyector de un elemento +### Leyendo valores específicos del inyector de un elemento {#reading-specific-values-from-an-elements-injector} Por defecto, el localizador de consulta indica tanto el elemento que estás buscando como el valor recuperado. Alternativamente puedes especificar la opción `read` para recuperar un valor diferente del elemento coincidente con el localizador. @@ -227,7 +227,7 @@ el `TemplateRef` asociado con ese elemento. Los desarrolladores más comúnmente usan `read` para recuperar `ElementRef` y `TemplateRef`. -### Descendientes de contenido +### Descendientes de contenido {#content-descendants} Por defecto, las consultas de `contentChildren` encuentran solo hijos _directos_ del componente y no atraviesan hacia los descendientes. Las consultas de `contentChild` sí atraviesan hacia los descendientes por defecto. @@ -258,14 +258,14 @@ En el ejemplo anterior, `CustomExpando` no puede encontrar `` con Las consultas de vista no tienen esta opción porque _siempre_ atraviesan hacia los descendientes. -## Consultas basadas en decoradores +## Consultas basadas en decoradores {#decorator-based-queries} CONSEJO: Aunque el equipo de Angular recomienda usar la función de consulta basada en signals para proyectos nuevos, las APIs de consulta basadas en decoradores originales permanecen completamente soportadas. Alternativamente puedes declarar consultas añadiendo el decorador correspondiente a una propiedad. Las consultas basadas en decoradores se comportan de la misma manera que las consultas basadas en signals excepto como se describe a continuación. -### Consultas de vista +### Consultas de vista {#decorator-view-queries} Puedes consultar por un solo resultado con el decorador `@ViewChild`. @@ -328,7 +328,7 @@ export class CustomCard { `@ViewChildren` crea un objeto `QueryList` que contiene los resultados de la consulta. Puedes suscribirte a cambios en los resultados de la consulta a lo largo del tiempo a través de la propiedad `changes`. -### Consultas de contenido +### Consultas de contenido {#decorator-content-queries} Puedes consultar por un solo resultado con el decorador `@ContentChild`. @@ -411,11 +411,11 @@ export class UserProfile { } `@ContentChildren` crea un objeto `QueryList` que contiene los resultados de la consulta. Puedes suscribirte a cambios en los resultados de la consulta a lo largo del tiempo a través de la propiedad `changes`. -### Opciones de consulta basadas en decoradores +### Opciones de consulta basadas en decoradores {#decorator-based-query-options} Todos los decoradores de consulta aceptan un objeto de opciones como segundo parámetro. Estas opciones funcionan de la misma manera que las consultas basadas en signals excepto donde se describe a continuación. -### Consultas estáticas +### Consultas estáticas {#static-queries} Los decoradores `@ViewChild` y `@ContentChild` aceptan la opción `static`. @@ -439,7 +439,7 @@ Los resultados de consultas estáticas no se actualizan después de la inicializ La opción `static` no está disponible para las consultas `@ViewChildren` y `@ContentChildren`. -### Usando QueryList +### Usando QueryList {#using-querylist} `@ViewChildren` y `@ContentChildren` ambos proporcionan un objeto `QueryList` que contiene una lista de resultados. @@ -447,7 +447,7 @@ La opción `static` no está disponible para las consultas `@ViewChildren` y `@C Puedes suscribirte a la propiedad `changes` para hacer algo cada vez que los resultados cambien. -## Errores comunes con consultas +## Errores comunes con consultas {#common-query-pitfalls} Al usar consultas, errores comunes pueden hacer que tu código sea más difícil de entender y mantener. diff --git a/adev-es/src/content/guide/components/selectors.en.md b/adev-es/src/content/guide/components/selectors.en.md index b473b4d1..a04926ba 100644 --- a/adev-es/src/content/guide/components/selectors.en.md +++ b/adev-es/src/content/guide/components/selectors.en.md @@ -6,17 +6,17 @@ Every component defines a [CSS selector](https://developer.mozilla.org/docs/Web/CSS/CSS_selectors) that determines how the component is used: - +```angular-ts {highlight: [2]} @Component({ selector: 'profile-photo', ... }) export class ProfilePhoto { } - +``` You use a component by creating a matching HTML element in the templates of _other_ components: - +```angular-ts {highlight: [3]} @Component({ template: ` @@ -24,7 +24,7 @@ You use a component by creating a matching HTML element in the templates of _oth ..., }) export class UserProfile { } - +``` **Angular matches selectors statically at compile-time**. Changing the DOM at run-time, either via Angular bindings or with DOM APIs, does not affect the components rendered. @@ -63,13 +63,13 @@ You can append this pseudo-class to any other selector to narrow which elements selector matches. For example, you could define a `[dropzone]` attribute selector and prevent matching `textarea` elements: - +```angular-ts {highlight: [2]} @Component({ selector: '[dropzone]:not(textarea)', ... }) export class DropZone { } - +``` Angular does not support any other pseudo-classes or pseudo-elements in component selectors. @@ -78,23 +78,23 @@ Angular does not support any other pseudo-classes or pseudo-elements in componen You can combine multiple selectors by concatenating them. For example, you can match `