-
When performance matters
-
+
-
+
+
+
What 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 ?
+
+ Learn more
+
diff --git a/adev/src/app/features/home/home.component.scss b/adev/src/app/features/home/home.component.scss
index 251fb66778cb..c3415485b83a 100644
--- a/adev/src/app/features/home/home.component.scss
+++ b/adev/src/app/features/home/home.component.scss
@@ -1,116 +1,216 @@
@use '@angular/docs/styles/media-queries' as mq;
+@mixin background-pattern {
+ pointer-events: none;
+ position: absolute;
+ height: 100%;
+ mask-image: var(--pattern-url);
+ mask-size: 22px 22px;
+ mask-repeat: repeat;
+}
+
:host {
width: 100%;
position: relative;
display: block;
-}
-.banners-layer {
- z-index: 10;
+ --card-background: var(--page-background);
+ --pattern-url: url("data:image/svg+xml, ");
+}
- .adev-banner-container {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- position: absolute;
- top: var(--layout-padding);
- left: calc(var(--layout-padding) + var(--primary-nav-width));
+.adev-banner-container {
+ margin-top: 3rem;
+ padding: 0 3rem;
+ display: flex;
+ justify-content: space-between;
+ gap: 0.5rem;
+ top: var(--layout-padding);
+ left: calc(var(--layout-padding) + var(--primary-nav-width));
+ z-index: 1;
+
+ @include mq.for-tablet-down {
+ justify-content: flex-start;
+ margin-top: 1rem;
+ }
+}
- @include mq.for-tablet-landscape-down {
- top: 6rem;
- left: var(--layout-padding);
- /* Assuming the container width is identical to the viewport width (mobile device). */
- max-width: calc(100% - var(--layout-padding) * 2);
+.adev-banner {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ border: 1px solid var(--senary-contrast);
+ background: var(--page-background);
+ border-radius: 0.25rem;
+ padding: 10px;
+ max-width: 100%;
+ width: fit-content;
+ box-sizing: border-box;
+
+ h1,
+ p {
+ display: inline;
+ font-size: 0.875rem;
+ margin: 0;
+ background-image: var(--red-to-pink-to-purple-horizontal-gradient);
+ background-clip: text;
+ color: transparent;
+ width: fit-content;
+ box-shadow: none;
+ position: relative;
+
+ &.adev-banner-cta {
+ color: var(--tertiary-contrast);
+
+ &::after {
+ content: '';
+ position: absolute;
+ width: 100%;
+ transform: scaleX(0);
+ height: 1px;
+ bottom: -2px;
+ left: 0;
+ background: var(--red-to-pink-to-purple-horizontal-gradient);
+ transform-origin: bottom right;
+ transition: transform 0.3s ease;
+ }
}
+ }
- @include mq.for-phone-only {
- & {
- top: 5rem;
+ &:hover {
+ .adev-banner-cta {
+ &::after {
+ transform: scaleX(1);
+ transform-origin: bottom left;
}
}
}
+}
- .adev-banner {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 0.5rem;
- border: 1px solid var(--senary-contrast);
- background: var(--page-background);
- border-radius: 0.25rem;
- padding: 10px;
- max-width: 100%;
- width: fit-content;
- box-sizing: border-box;
+section {
+ @include mq.for-tablet-landscape-up {
+ padding-left: 7rem;
+ }
+ text-align: center;
+ h2 {
+ padding: 0 1rem;
+ font-size: clamp(1rem, 9vw, 38pt);
+ }
+}
- h1,
- p {
- display: inline;
- font-size: 0.875rem;
- margin: 0;
- background-image: var(--red-to-pink-to-purple-horizontal-gradient);
- background-clip: text;
- color: transparent;
- width: fit-content;
- font-weight: 500;
- box-shadow: none;
- position: relative;
+@property --gradient-angle {
+ syntax: '';
+ initial-value: 90deg;
+ inherits: false;
+}
- &.adev-banner-cta {
- color: var(--tertiary-contrast);
+@keyframes rotateGradient {
+ from {
+ --gradient-angle: 0deg;
+ }
+ to {
+ --gradient-angle: 360deg;
+ }
+}
- &::after {
- content: '';
- position: absolute;
- width: 100%;
- transform: scaleX(0);
- height: 1px;
- bottom: -2px;
- left: 0;
- background: var(--red-to-pink-to-purple-horizontal-gradient);
- transform-origin: bottom right;
- transition: transform 0.3s ease;
- }
- }
- }
+@property --pulse-inner {
+ syntax: '';
+ initial-value: 20%;
+ inherits: false;
+}
- &:hover {
- .adev-banner-cta {
- &::after {
- transform: scaleX(1);
- transform-origin: bottom left;
- }
- }
- }
+@property --pulse-outer {
+ syntax: '';
+ initial-value: 75%;
+ inherits: false;
+}
+
+@keyframes pulse {
+ 0% {
+ --pulse-inner: 30%;
+ --pulse-outer: 75%;
+ }
+ 50% {
+ --pulse-inner: 30%;
+ --pulse-outer: 60%;
+ }
+ 100% {
+ --pulse-inner: 30%;
+ --pulse-outer: 75%;
}
}
.logo-section {
- text-align: center;
- background-image:
- linear-gradient(
- 63deg,
- transparent 66%,
- var(--page-background) 69%,
- var(--page-background) 70%,
- transparent 70%
- ),
- linear-gradient(162deg, var(--page-background) 50%, #9337f58a 100%);
- background-size:
- 27px 27px,
- auto;
- background-repeat: repeat;
- background-color: transparent;
- padding-bottom: 36px;
- padding-top: 7rem;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ height: min(700px, 100vh);
+ @include mq.for-tablet-down {
+ height: min(700px, calc(100vh - 60px));
+ }
+
+ .pattern {
+ @include background-pattern;
+ width: calc(100% - 7rem);
+ @include mq.for-tablet-landscape-down {
+ width: 100%;
+ }
+ animation: rotateGradient 10s linear infinite;
+
+ background: conic-gradient(
+ from 122deg at 50% 50%,
+ transparent 17%,
+ #f627e3 25%,
+ #6911d2 32%,
+ transparent 38%,
+ transparent 61%,
+ #6911d2 71%,
+ #f627e3 81%,
+ transparent 91%
+ );
+
+ &:after {
+ // A fading bottom for the pattern
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(180deg, transparent, var(--page-background));
+ bottom: 0;
+ height: 50px;
+ top: auto;
+ }
+ }
+
+ .search-field {
+ @include mq.for-tablet-down() {
+ display: none;
+ }
+ }
+
+ .content {
+ z-index: 1;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+
+ h2 {
+ font-size: clamp(3rem, 6vw, 5rem);
+ margin-bottom: 0;
+ }
+
+ h3 {
+ font-size: clamp(0.5rem, 4vw, 1rem);
+ color: var(--quaternary-contrast);
+ }
+ }
@include mq.for-tablet-landscape-down {
- padding-top: 9rem;
+ margin-top: 4rem;
}
button {
- font-size: 1.2rem;
+ margin-bottom: 5rem;
@include mq.for-tablet-down {
font-size: 1rem;
@@ -141,47 +241,178 @@
}
}
}
+
+ .uwu img {
+ width: 50vw;
+ }
}
-section {
- padding-left: 9rem;
- padding-right: 2rem;
+.features-section {
+ [ngTabs] {
+ overflow: hidden;
+ width: 100%;
+ border-radius: 0.5rem;
+ display: flex;
+ flex-direction: column;
+ }
- h2 {
- font-size: 2.5rem;
- margin-bottom: 3rem;
+ [ngTabList] {
+ padding: 4px;
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 0;
+ list-style: none;
+ position: relative;
+ background: var(--card-background);
+ border: 1px solid var(--quinary-contrast);
+ border-radius: 0.5rem;
+ width: fit-content;
+ margin: 0 auto 2rem;
+
+ .tab-background {
+ position: absolute;
+ top: 4px;
+ bottom: 4px;
+ left: 4px;
+ width: calc((100% - 8px) / 4);
+ background-color: var(--septenary-contrast);
+ border-radius: 0.5rem;
+ transition: transform 0.2s ease-in-out;
+ }
+
+ &:has(> :nth-child(2)[aria-selected='true']) .tab-background {
+ transform: translateX(0%);
+ }
+ &:has(> :nth-child(3)[aria-selected='true']) .tab-background {
+ transform: translateX(100%);
+ }
+ &:has(> :nth-child(4)[aria-selected='true']) .tab-background {
+ transform: translateX(200%);
+ }
+ &:has(> :nth-child(5)[aria-selected='true']) .tab-background {
+ transform: translateX(300%);
+ }
}
- svg {
- fill: var(--always-pink);
- color: var(--always-pink);
+ [ngTab] {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ outline: none;
+ padding: 0.5rem;
+ cursor: pointer;
+ color: var(--tertiary-contrast);
+ font-size: 14px;
+ border-radius: 0.5rem;
+ transition: all 0.2s ease-in-out;
+ position: relative;
+ z-index: 1;
+
+ &:hover {
+ color: var(--primary-contrast);
+ }
}
- .section-content {
- max-width: 1200px;
- margin: 0 auto;
+ [ngTab][aria-selected='true'] {
+ color: var(
+ --electric-violet
+ ); // the gradient would "look" better but fails constrast requirements
}
- @include mq.for-tablet-landscape-down {
- padding-left: 2rem;
+ .sliding-window {
+ width: 400%;
+ display: flex;
+ transition: all 0.2s ease-in-out;
+ }
+
+ [ngTabList]:has(> :nth-child(2)[aria-selected='true']) ~ .sliding-window {
+ transform: translateX(0%);
+ }
+ [ngTabList]:has(> :nth-child(3)[aria-selected='true']) ~ .sliding-window {
+ transform: translateX(-25%);
+ }
+ [ngTabList]:has(> :nth-child(4)[aria-selected='true']) ~ .sliding-window {
+ transform: translateX(-50%);
+ }
+ [ngTabList]:has(> :nth-child(5)[aria-selected='true']) ~ .sliding-window {
+ transform: translateX(-75%);
+ }
+
+ [ngTabPanel] {
+ display: grid;
+ place-items: start center;
+ padding: 1rem 3rem;
+ min-height: 100px;
+ width: 25%;
+ box-sizing: border-box;
}
}
-.features {
- padding-block: 2rem;
- text-align: center;
+.two-column-step {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 3rem;
+ text-align: left;
+ width: 100%;
+ max-width: 1200px;
+ margin: 0 auto;
+ box-sizing: border-box;
+ background-color: var(--card-background);
+ border: 1px solid var(--senary-contrast);
+ border-radius: 0.5rem;
+ padding: 2rem;
+
+ @include mq.for-tablet-down {
+ grid-template-columns: 1fr;
+ gap: 2rem;
+ padding: 1.5rem;
+ }
+}
- h2 {
- font-size: 2.2rem;
+.step-description {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+
+ .header {
+ font-size: 1.75rem;
+ }
+
+ .description {
+ font-size: 1.1rem;
+ line-height: 1.6;
+ color: var(--secondary-contrast);
+
+ .code {
+ font-family: monospace;
+ background: rgba(128, 128, 128, 0.2);
+ padding: 0.1rem 0.3rem;
+ border-radius: 0.5rem;
+ margin: 0 0.1rem;
+ }
}
+}
- .features-grid {
+.step-code {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.selling-points {
+ max-width: 1440px;
+ margin: 0 auto 6rem;
+ box-sizing: border-box;
+
+ .cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
- max-width: 1200px;
- margin: 0 auto;
+ gap: 1.5rem;
+ text-align: left; /* reset center alignment from section */
+ overflow-x: auto;
+ margin: 0 1rem;
- @include mq.for-tablet-down {
+ @include mq.for-tablet-landscape-down {
grid-template-columns: repeat(2, 1fr);
}
@@ -190,183 +421,420 @@ section {
}
}
- .feature {
+ .card {
+ background-color: var(--card-background);
+ border: 1px solid var(--senary-contrast);
+ border-radius: 0.5rem;
padding: 2rem;
- border-left: 1px solid var(--senary-contrast);
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ height: 100%;
+ box-sizing: border-box;
+ transition: border-color 0.3s ease;
- &:first-child {
- border-left: none;
+ &:hover {
+ border-color: var(--quinary-contrast);
+ background: var(--octonary-contrast);
+ cursor: pointer;
}
+ }
- @include mq.for-tablet-down {
- border-top: 1px solid var(--senary-contrast);
-
- &:first {
- border-top: none;
- }
+ .icon-wrapper {
+ width: 48px;
+ height: 48px;
+ border-radius: 0.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--primary-contrast);
+ position: relative;
+ background: linear-gradient(
+ 90deg,
+ var(--orange-red) 0%,
+ var(--vivid-pink) 50%,
+ var(--electric-violet) 100%
+ );
+
+ &::after {
+ content: '';
+ position: absolute;
+ inset: 1px;
+ background: var(--card-background);
+ border-radius: calc(0.5rem - 1px);
+ }
- &:nth-child(odd) {
- border-left: none;
- }
+ docs-icon {
+ font-size: 24px;
+ position: relative;
+ z-index: 1;
}
+ }
- @include mq.for-phone-only {
- &:nth-child(n) {
- border-left: none;
- }
+ .card-content {
+ h3 {
+ font-size: 1rem;
+ line-height: 1.5;
+ color: var(--secondary-contrast);
+ margin: 0;
- &:nth-child(-n + 1) {
- border-top: none;
+ strong {
+ color: var(--primary-contrast);
+ font-weight: 600;
}
}
+ }
+}
+
+.performance-section {
+ display: flex;
+ justify-content: center;
+ align-items: center;
- svg {
- width: 3rem;
+ .wrapper {
+ width: 800px;
+ height: 800px;
+ position: relative;
+ display: flex;
+ justify-content: center;
+
+ @include mq.for-phone-only {
+ width: 400px;
+ height: 400px;
}
+ }
- h3 {
- font-size: 1.5rem;
- margin-bottom: 1rem;
+ .pattern {
+ @include background-pattern;
+ z-index: -1;
+ width: 100%;
+
+ background: radial-gradient(transparent 28%, #f627e3 45%, #6911d2 55%, transparent 69%);
+ @include mq.for-phone-only {
+ opacity: 0.7;
}
+ }
- p {
- font-size: 1rem;
+ .content {
+ height: 100%;
+ max-width: 500px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ h2 {
+ margin-bottom: 0;
+ }
+ h3 {
+ font-size: 12pt;
color: var(--quaternary-contrast);
- font-weight: 300;
+ max-width: 400px;
+ margin: 0 1rem;
+ }
+ button {
+ margin-top: 2rem;
}
}
}
-.performance {
- background-image:
- linear-gradient(
- 63deg,
- transparent 66%,
- var(--page-background) 69%,
- var(--page-background) 70%,
- transparent 70%
- ),
- linear-gradient(350deg, var(--page-background) 50%, #9337f58a 100%);
- background-size:
- 27px 27px,
- auto;
- background-repeat: repeat;
- background-color: transparent;
-
- .section-content {
- display: flex;
- flex-wrap: wrap;
- gap: 1.5rem;
+.learn-more-bottom-section {
+ position: relative;
+ display: flex;
+ height: min(500px, 100vh);
+
+ .pattern {
+ @include background-pattern;
+ width: calc(100% - 7rem);
+
+ animation: pulse 4s ease-in-out infinite;
+ background: radial-gradient(
+ circle at 50% 125%,
+ transparent 20%,
+ #f627e3 35%,
+ #6911d2 55%,
+ transparent var(--pulse-outer)
+ );
+
+ @include mq.for-tablet-landscape-down {
+ width: 100%;
+ }
}
- .performance-half {
- flex: 1 0 200px;
- padding: 3rem 0;
+ .content {
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: auto;
+ max-width: 600px;
- &:nth-child(2) {
- display: flex;
- justify-content: center;
- align-items: center;
+ h2 {
+ margin-bottom: 0rem;
+ }
- @include mq.for-desktop-down {
- display: none;
- }
+ h3 {
+ font-size: 12pt;
+ margin-bottom: 3rem;
+ }
- svg {
- width: 80%;
- max-width: 365px;
- }
+ button {
+ position: absolute;
+ bottom: 3rem;
}
}
+}
- p {
- &.secondary {
- font-size: 1.2rem;
+.explore-section {
+ max-width: 1440px;
+ margin: 0 auto;
+ box-sizing: border-box;
+
+ .title {
+ position: relative;
+ height: clamp(150px, 40vw, 400px);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ h2 {
+ max-width: 512px;
+ z-index: 1;
+ position: relative;
+ }
+
+ .pattern {
+ @include background-pattern;
+ height: 120%;
+ width: 100%;
+ animation: rotateGradient 10s linear infinite;
+
+ background: radial-gradient(circle at 50% 150%, #f627e3 50%, #6911d2 66%, transparent 75%);
}
}
- .docs-card-grid {
+ .cards-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
- gap: 1.5rem;
+ grid-gap: 2rem;
+ margin: 0 1rem;
- @include mq.for-tablet-down {
+ @include mq.for-phone-only {
grid-template-columns: 1fr;
}
}
-}
-.learn-more {
- background-image:
- linear-gradient(
- 63deg,
- transparent 66%,
- var(--page-background) 69%,
- var(--page-background) 70%,
- transparent 70%
- ),
- linear-gradient(162deg, var(--page-background) 50%, #9337f58a 100%);
- background-size:
- 27px 27px,
- auto;
- background-repeat: repeat;
- background-color: transparent;
-
- padding-block: 5rem;
- text-align: left;
+ .explore-card {
+ display: flex;
+ flex-direction: column;
+ background: var(--card-background);
+ border: 1px solid var(--senary-contrast);
+ border-radius: 1rem;
+ overflow: hidden;
+ text-decoration: none;
+ transition: border-color 0.3s ease;
+ height: 100%;
+ min-height: 500px;
+ position: relative;
+ cursor: pointer;
+ animation: rotateGradient 6s linear infinite;
+
+ &::after {
+ content: '';
+ position: absolute;
+ inset: 1px;
+ background: var(--card-background);
+ border-radius: calc(1rem - 1px); // looks better
+ }
- h2 {
- font-size: 2.5rem;
- margin-bottom: 3rem;
- }
+ &:hover {
+ .fake-button {
+ // we're using a background-position to animate on hover
+ // because using background-clip with transform causes rendering/flicker issues in some browsers
+ background-position: 100% 0%;
+
+ docs-icon {
+ transform: translateX(4px);
+ color: var(--electric-violet);
+ }
+ }
- h3 {
- margin-top: 0;
- }
+ background: linear-gradient(
+ var(--gradient-angle),
+ var(--orange-red) 0%,
+ var(--electric-violet) 39%,
+ transparent 81%
+ );
+ }
- svg {
- width: 64px;
- flex: 0 0 64px;
- margin: 0 1rem;
+ .card-content {
+ display: flex;
+ flex-direction: column;
+ text-align: left;
+ padding: 2.5rem;
+ z-index: 2;
+ flex: 1;
+
+ p {
+ color: var(--quaternary-contrast);
+ line-height: 1.5;
+ margin-bottom: 1.5rem;
+ font-size: 1rem;
+ .emphasis {
+ color: var(--secondary-contrast);
+ }
+ }
+ }
+
+ .fake-button {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--tertiary-contrast);
+ transition: color 0.3s ease;
+ background: linear-gradient(
+ 90deg,
+ var(--tertiary-contrast) 0%,
+ var(--tertiary-contrast) 47%,
+ var(--orange-red) 51%,
+ var(--electric-violet) 100%
+ );
+ background-size: 200% 100%;
+ background-position: 0% 0%;
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ transition: background-position 0.5s ease;
+
+ docs-icon {
+ font-size: 1.2rem;
+ color: var(--tertiary-contrast);
+ transition:
+ transform 0.3s ease,
+ color 0.3s ease;
+ }
+ }
+
+ .editor-background {
+ --img: url('../../../assets/images/home/editor-light.png');
+ background-image: linear-gradient(135deg, transparent, var(--card-background)), var(--img);
+ position: absolute;
+ bottom: 1px;
+ right: 1px;
+ width: 50%;
+ height: clamp(100px, 25vw, 300px);
+ background-size: cover;
+ border-bottom-right-radius: 1rem;
+
+ @include mq.for-phone-only {
+ height: 50vw;
+ }
+
+ ::ng-deep .docs-dark-mode & {
+ --img: url('../../../assets/images/home/editor-dark.png') !important;
+ }
+ }
+
+ .logo-background {
+ --logo-svg: url('../../../assets/images/home/angular-logo-light.svg');
+ background-image: var(--logo-svg);
+ position: absolute;
+ bottom: -100px;
+ right: -100px;
+ width: 490px;
+ height: 453px;
+
+ ::ng-deep .docs-dark-mode & {
+ --logo-svg: url('../../../assets/images/home/angular-logo-dark.svg') !important;
+ }
+ }
}
+}
- p {
- font-size: 1rem;
- font-weight: 300;
+.angular-mcp-container {
+ position: relative;
+ z-index: 1;
+ max-width: 800px;
+ margin: auto;
+ border-radius: 1rem;
+
+ // Glowing gradient background
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ border-radius: inherit;
+ background: linear-gradient(90deg, #a800d6 0%, #ff266f 50%, #ff5c39 100%);
+ filter: blur(40px);
+ opacity: 0.25;
+ transform: scale(0.7);
+ transition: all 0.5s ease;
+ }
- &.secondary {
- color: var(--quaternary-contrast);
+ .explore-card:hover & {
+ &::before {
+ transform: scale(0.95);
+ opacity: 0.45;
}
}
- .card-content {
- flex: 1;
+ .mcp-content {
display: flex;
- flex-direction: column;
+ align-items: center;
justify-content: space-between;
- height: 100%;
+ padding: 1rem;
+ // background-color: var(--card-background);
+ // border: 1px solid var(--senary-contrast);
+ border-radius: 1rem;
+ width: 100%;
+ box-sizing: border-box;
+ animation: rotateGradient 10s linear infinite;
+
+ background: conic-gradient(
+ from var(--gradient-angle) at 50% 50%,
+ var(--hot-red) 0%,
+ var(--vivid-pink) 20%,
+ var(--electric-violet) 40%,
+ transparent 50%,
+ transparent 85%,
+ var(--hot-red) 100%
+ );
+
+ &::after {
+ content: '';
+ position: absolute;
+ inset: 1px;
+ background: var(--card-background);
+ border-radius: 1rem;
+ }
}
- .docs-card {
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- gap: 1.5rem;
-
- @include mq.for-tablet-down {
- & {
- flex-direction: column;
- }
- }
+ .mcp-label {
+ color: var(--primary-contrast);
+ margin-right: 1rem;
+ white-space: nowrap;
+ font-size: 0.9rem;
+ z-index: 1;
}
- .docs-card-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 1.5rem;
+ .mcp-pills {
+ font-size: 0.7rem;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+ overflow: hidden;
+ max-height: 2rem;
+ z-index: 1;
+ }
- @include mq.for-tablet-down {
- grid-template-columns: 1fr;
- }
+ .mcp-pill {
+ background-color: var(--septenary-contrast);
+ color: var(--vivid-pink);
+ font-family: monospace;
+ padding: 0.5rem;
+ border-radius: 8px;
}
}
diff --git a/adev/src/app/features/home/home.component.ts b/adev/src/app/features/home/home.component.ts
index 5c7c61c07741..cff34b33e5fd 100644
--- a/adev/src/app/features/home/home.component.ts
+++ b/adev/src/app/features/home/home.component.ts
@@ -6,12 +6,34 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import {Tab, TabContent, TabList, TabPanel, Tabs} from '@angular/aria/tabs';
+import {A11yModule} from '@angular/cdk/a11y';
import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
+import {IS_SEARCH_DIALOG_OPEN, IconComponent, TextField} from '@angular/docs';
import {ActivatedRoute, RouterLink} from '@angular/router';
+import {CodeHighligher} from './code-highlighting/code-highlighter';
+import {ControlFlowExample} from './components/control-flow/control-flow-example';
+import {DeferrableViewsExample} from './components/deferrable-views-example/deferrable-views-example';
+import {HydrationExample} from './components/hydration-example/hydration-example';
+import {SignalsDemo} from './components/signals-demo/signals-demo';
@Component({
selector: 'adev-home',
- imports: [RouterLink],
+ imports: [
+ RouterLink,
+ TextField,
+ TabList,
+ Tab,
+ Tabs,
+ TabPanel,
+ TabContent,
+ IconComponent,
+ A11yModule,
+ SignalsDemo,
+ ControlFlowExample,
+ DeferrableViewsExample,
+ HydrationExample,
+ ],
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -19,4 +41,11 @@ import {ActivatedRoute, RouterLink} from '@angular/router';
export default class Home {
private readonly activatedRoute = inject(ActivatedRoute);
protected readonly isUwu = 'uwu' in this.activatedRoute.snapshot.queryParams;
+ private readonly codeHighlighter = inject(CodeHighligher);
+
+ protected readonly displaySearchDialog = inject(IS_SEARCH_DIALOG_OPEN);
+
+ openSearch() {
+ this.displaySearchDialog.set(true);
+ }
}
diff --git a/adev/src/app/features/not-found/not-found.html b/adev/src/app/features/not-found/not-found.html
new file mode 100644
index 000000000000..a4398293ef2f
--- /dev/null
+++ b/adev/src/app/features/not-found/not-found.html
@@ -0,0 +1,81 @@
+
+
+
+ We couldn't find what you were looking for. We have initiated a search for the term extracted
+ from the URL.
+
+
+ If you think this is a mistake, please
+
+ open an issue
+ so we can fix it.
+
+
+
+ @if (searchResults().length > 0) {
+
+
+
+
Feeling lucky?
+
Maybe what you're looking for is in the list below:
+
+
+
+ }
+
diff --git a/adev/src/app/features/not-found/not-found.scss b/adev/src/app/features/not-found/not-found.scss
new file mode 100644
index 000000000000..209dda0a5926
--- /dev/null
+++ b/adev/src/app/features/not-found/not-found.scss
@@ -0,0 +1,130 @@
+:host {
+ display: block;
+ padding-top: var(--layout-padding);
+ padding-bottom: var(--layout-padding);
+}
+
+.docs-viewer {
+ display: flex;
+ flex-direction: column;
+ padding: 0px;
+ box-sizing: border-box;
+ padding-inline: var(--layout-padding);
+}
+
+.enter-animation {
+ animation: slide-fade 1s;
+}
+@keyframes slide-fade {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.docs-search-results {
+ list-style-type: none;
+ padding-inline: 0;
+ padding-block-start: 1rem;
+ padding-block-end: 1rem;
+ margin: 0;
+ max-width: 750px;
+
+ .docs-search-result {
+ border-inline-start: 2px solid var(--senary-contrast);
+ margin-inline-start: 1rem;
+ display: block;
+ font-size: 0.875rem;
+
+ .docs-search-result-icon {
+ display: inline-block;
+
+ i {
+ display: flex;
+ align-items: center;
+ font-size: 1.2rem;
+ }
+ }
+
+ /**
+ * This rule needs ng-deep to be applied to elements that are created via a [innerHTML] binding
+ */
+ ::ng-deep {
+ mark {
+ background: #e62600;
+ background: var(--red-to-orange-horizontal-gradient);
+ background-clip: text;
+ -webkit-background-clip: text;
+ color: transparent;
+ }
+ }
+
+ a {
+ color: var(--secondary-contrast);
+ display: flex;
+ justify-content: space-between;
+ padding: 1rem;
+ gap: 0.5rem;
+ }
+
+ &__label,
+ &__sub-label,
+ &__content {
+ transition: color 0.3s ease;
+ }
+
+ &__label,
+ &__sub-label {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ margin: 0;
+ }
+
+ &__label {
+ font-weight: 600;
+ flex-wrap: wrap;
+
+ &__package {
+ font-size: 0.75rem;
+ }
+ }
+
+ &__content,
+ &__category {
+ margin: 0;
+ }
+
+ &__sub-label,
+ &__content {
+ margin-block-start: 0.375rem;
+ margin-inline-start: 2rem;
+ color: var(--quaternary-contrast);
+ }
+
+ &__category {
+ font-weight: 400;
+ color: var(--quaternary-contrast);
+ }
+
+ &.active {
+ background-color: var(--septenary-contrast); // stylelint-disable-line
+ }
+
+ &:hover,
+ &.active {
+ background-color: var(--octonary-contrast); // stylelint-disable-line
+ border-inline-start: 2px solid var(--primary-contrast);
+
+ .docs-search-result__label,
+ .docs-search-result__sub-label,
+ .docs-search-result__content {
+ color: var(--primary-contrast);
+ }
+ }
+ }
+}
diff --git a/adev/src/app/features/not-found/not-found.ts b/adev/src/app/features/not-found/not-found.ts
new file mode 100644
index 000000000000..335ca1633a93
--- /dev/null
+++ b/adev/src/app/features/not-found/not-found.ts
@@ -0,0 +1,37 @@
+/*!
+ * @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 {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
+import {ActivatedRoute, RouterLink, UrlSegment} from '@angular/router';
+import {Search, SearchItem, RelativeLink, SearchResultItem} from '@angular/docs';
+
+@Component({
+ imports: [SearchItem, RouterLink, RelativeLink],
+ templateUrl: './not-found.html',
+ styleUrl: './not-found.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class NotFound {
+ private readonly search = inject(Search);
+ protected readonly searchResults = signal([]);
+
+ constructor() {
+ const activatedRoute = inject(ActivatedRoute);
+ const searchTerms = this.extractSearchTerm(activatedRoute.snapshot.url);
+ if (searchTerms) {
+ // We're using the one-shot query request to not interfere with the main search signal
+ this.search.searchWithQuery(searchTerms).then((results) => {
+ this.searchResults.set(results ?? []);
+ });
+ }
+ }
+
+ private extractSearchTerm(url: UrlSegment[]): string {
+ return url.join(' ').replace(/[-_/]/g, ' ');
+ }
+}
diff --git a/adev/src/app/features/playground/playground.component.spec.ts b/adev/src/app/features/playground/playground.component.spec.ts
index 5cdd88f17613..723834ea7956 100644
--- a/adev/src/app/features/playground/playground.component.spec.ts
+++ b/adev/src/app/features/playground/playground.component.spec.ts
@@ -26,7 +26,7 @@ describe('TutorialPlayground', () => {
},
};
- beforeEach(() => {
+ beforeEach(async () => {
class FakeEmbeddedTutorialManager {
fetchAndSetTutorialFiles() {}
}
@@ -50,7 +50,7 @@ describe('TutorialPlayground', () => {
fixture = TestBed.createComponent(TutorialPlayground);
component = fixture.componentInstance;
- fixture.detectChanges();
+ await fixture.whenStable();
});
it('should create', () => {
diff --git a/adev/src/app/features/references/api-item-label/api-item-label.component.spec.ts b/adev/src/app/features/references/api-item-label/api-item-label.component.spec.ts
index 0c794eb6ecdc..76dbbecd5a2f 100644
--- a/adev/src/app/features/references/api-item-label/api-item-label.component.spec.ts
+++ b/adev/src/app/features/references/api-item-label/api-item-label.component.spec.ts
@@ -15,24 +15,21 @@ describe('ApiItemLabel', () => {
let fixture: ComponentFixture;
beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [ApiItemLabel],
- });
fixture = TestBed.createComponent(ApiItemLabel);
});
- it('should by default display short label for Class', () => {
+ it('should by default display short label for Class', async () => {
fixture.componentRef.setInput('type', ApiItemType.CLASS);
- fixture.detectChanges();
+ await fixture.whenStable();
const label = fixture.nativeElement.innerText;
expect(label).toBe('C');
});
- it('should display short label for Class', () => {
+ it('should display short label for Class', async () => {
fixture.componentRef.setInput('type', ApiItemType.CLASS);
- fixture.detectChanges();
+ await fixture.whenStable();
const label = fixture.nativeElement.innerText;
diff --git a/adev/src/app/features/references/api-items-section/api-items-section.component.html b/adev/src/app/features/references/api-items-section/api-items-section.component.html
index 8df416950bc6..602719ab5d21 100644
--- a/adev/src/app/features/references/api-items-section/api-items-section.component.html
+++ b/adev/src/app/features/references/api-items-section/api-items-section.component.html
@@ -7,7 +7,7 @@
class="adev-api-anchor"
tabindex="-1"
>
- {{group().title}}
+ {{ group().title }}
@@ -29,13 +29,15 @@
{{ apiItem.title }}
@if (apiItem.deprecated) {
- <!>
+ DEPR
}
- @if(apiItem.experimental) {
- 🧪
+ @if (apiItem.experimental) {
+ EXP
}
- @if(apiItem.developerPreview) {
- 🚧
+ @if (apiItem.developerPreview) {
+
+ DEV
+
}
}
diff --git a/adev/src/app/features/references/api-items-section/api-items-section.component.scss b/adev/src/app/features/references/api-items-section/api-items-section.component.scss
index ee36fae6ccfe..6e9ae3cc5f17 100644
--- a/adev/src/app/features/references/api-items-section/api-items-section.component.scss
+++ b/adev/src/app/features/references/api-items-section/api-items-section.component.scss
@@ -1,5 +1,3 @@
-@use '@angular/docs/styles/anchor' as anchor;
-
.adev-api-items-section-header {
display: flex;
@@ -49,6 +47,7 @@
text-overflow: ellipsis;
box-sizing: border-box;
width: 100%;
+ justify-content: start;
a {
color: var(--quaternary-contrast);
@@ -75,23 +74,60 @@
.adev-api-items-section-item {
display: flex;
- flex: 1;
gap: 1em;
}
+:host {
+ --item-attr-base-mix: 80%;
+
+ .docs-light-mode & {
+ --item-attr-base-mix: 20%;
+ }
+ @media screen and (prefers-color-scheme: light) {
+ --item-attr-base-mix: 20%;
+ }
+}
+
+.docs-api-item-label {
+ pointer-events: none;
+}
+
.adev-item-attribute {
- width: 24px;
text-align: center;
- font-family: var(--code-font);
- background-color: var(--senary-contrast);
- color: var(--tertiary-contrast);
- border-radius: 0.25rem;
- padding: 0.001rem 0.2rem 0.005rem;
+ border-radius: 0.5rem;
+ padding: 0rem 0.375rem;
margin-inline-start: 0.5rem;
+ display: block;
+ font-size: 0.625rem;
+ line-height: 1rem;
+ cursor: default;
+ user-select: none;
+ font-weight: bold;
+
+ &.adev-dev-preview {
+ background: color-mix(
+ in srgb,
+ var(--symbolic-yellow) var(--item-attr-base-mix),
+ var(--octonary-contrast)
+ );
+ color: var(--gray-1000);
+ }
+
+ &.adev-experimental {
+ background: color-mix(
+ in srgb,
+ var(--symbolic-green) var(--item-attr-base-mix),
+ var(--octonary-contrast)
+ );
+ color: var(--gray-1000);
+ }
+
+ &.adev-deprecated {
+ background-color: var(--senary-contrast);
+ color: var(--tertiary-contrast);
+ }
}
.adev-api-anchor {
color: inherit;
-
- @include anchor.docs-anchor();
}
diff --git a/adev/src/app/features/references/api-items-section/api-items-section.component.spec.ts b/adev/src/app/features/references/api-items-section/api-items-section.component.spec.ts
index a20e52b44c23..85b6652f8dc7 100644
--- a/adev/src/app/features/references/api-items-section/api-items-section.component.spec.ts
+++ b/adev/src/app/features/references/api-items-section/api-items-section.component.spec.ts
@@ -54,18 +54,18 @@ describe('ApiItemsSection', () => {
component = fixture.componentInstance;
});
- it('should render list of all APIs of provided group', () => {
+ it('should render list of all APIs of provided group', async () => {
fixture.componentRef.setInput('group', fakeGroup);
- fixture.detectChanges();
+ await fixture.whenStable();
const apis = fixture.debugElement.queryAll(By.css('.adev-api-items-section-grid li'));
expect(apis.length).toBe(2);
});
- it('should display deprecated icon for deprecated API', () => {
+ it('should display deprecated icon for deprecated API', async () => {
fixture.componentRef.setInput('group', fakeGroup);
- fixture.detectChanges();
+ await fixture.whenStable();
const deprecatedApiIcons = fixture.debugElement.queryAll(
By.css('.adev-api-items-section-grid li .adev-item-attribute'),
diff --git a/adev/src/app/features/references/api-items-section/api-items-section.component.ts b/adev/src/app/features/references/api-items-section/api-items-section.component.ts
index 260dd8b323dc..87f00ac2af54 100644
--- a/adev/src/app/features/references/api-items-section/api-items-section.component.ts
+++ b/adev/src/app/features/references/api-items-section/api-items-section.component.ts
@@ -10,11 +10,11 @@ import {ChangeDetectionStrategy, Component, input} from '@angular/core';
import {RouterLink} from '@angular/router';
import {ApiItemsGroup} from '../interfaces/api-items-group';
import ApiItemLabel from '../api-item-label/api-item-label.component';
-import {MatTooltipModule} from '@angular/material/tooltip';
+import {MatTooltip} from '@angular/material/tooltip';
@Component({
selector: 'adev-api-items-section',
- imports: [ApiItemLabel, RouterLink, MatTooltipModule],
+ imports: [ApiItemLabel, RouterLink, MatTooltip],
templateUrl: './api-items-section.component.html',
styleUrls: ['./api-items-section.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.html b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.html
index 60afa622e9a1..b2ed207e3be1 100644
--- a/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.html
+++ b/adev/src/app/features/references/api-reference-details-page/api-reference-details-page.component.html
@@ -1,5 +1,6 @@
@if (docContent(); as docContent) {
- {
const harness = await RouterTestingHarness.create();
fixture = harness.fixture;
component = await harness.navigateByUrl('/', ApiReferenceDetailsPage);
- fixture.detectChanges();
});
it('should create', () => {
diff --git a/adev/src/app/features/references/api-reference-list/api-reference-list.component.html b/adev/src/app/features/references/api-reference-list/api-reference-list.component.html
index 4ee44f20b140..6b5d38678ee2 100644
--- a/adev/src/app/features/references/api-reference-list/api-reference-list.component.html
+++ b/adev/src/app/features/references/api-reference-list/api-reference-list.component.html
@@ -4,15 +4,21 @@ API Reference
-
+
+
+
- @for (stat of statuses | keyvalue:null; track $index) {
+ @for (stat of statuses | keyvalue: null; track $index) {
- {{statusLabels[stat.value]}}
+ {{ statusLabels[stat.value] }}
}
@@ -23,11 +29,11 @@
API Reference
@for (itemType of itemTypes; track itemType) {
- {{ itemType | adevApiLabel : 'full' }}
+ {{ itemType | adevApiLabel: 'full' }}
}
diff --git a/adev/src/app/features/references/api-reference-list/api-reference-list.component.spec.ts b/adev/src/app/features/references/api-reference-list/api-reference-list.component.spec.ts
index 62230adc0832..112611130f58 100644
--- a/adev/src/app/features/references/api-reference-list/api-reference-list.component.spec.ts
+++ b/adev/src/app/features/references/api-reference-list/api-reference-list.component.spec.ts
@@ -8,16 +8,16 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
-import ApiReferenceList, {ALL_TYPES_KEY, STATUSES} from './api-reference-list.component';
-import {ApiReferenceManager} from './api-reference-manager.service';
-import {signal} from '@angular/core';
-import {ApiItemType} from '../interfaces/api-item-type';
-import {RouterTestingHarness} from '@angular/router/testing';
-import {provideRouter} from '@angular/router';
import {Location} from '@angular/common';
+import {signal} from '@angular/core';
+import {TextField} from '@angular/docs';
import {By} from '@angular/platform-browser';
-import {NavigationItem, TextField} from '@angular/docs';
+import {provideRouter} from '@angular/router';
+import {RouterTestingHarness} from '@angular/router/testing';
import {ApiItem} from '../interfaces/api-item';
+import {ApiItemType} from '../interfaces/api-item-type';
+import ApiReferenceList, {ALL_TYPES_KEY, STATUSES} from './api-reference-list.component';
+import {ApiReferenceManager} from './api-reference-manager.service';
describe('ApiReferenceList', () => {
let component: ApiReferenceList;
@@ -93,7 +93,7 @@ describe('ApiReferenceList', () => {
}),
};
- beforeEach(() => {
+ beforeEach(async () => {
TestBed.configureTestingModule({
imports: [ApiReferenceList],
providers: [
@@ -103,43 +103,52 @@ describe('ApiReferenceList', () => {
});
fixture = TestBed.createComponent(ApiReferenceList);
component = fixture.componentInstance;
- fixture.detectChanges();
+ await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
- it('should display only items which contains provided query when query is not empty', () => {
+ it('should display only items which contains provided query when query is not empty', async () => {
fixture.componentRef.setInput('query', 'Item1');
- fixture.detectChanges();
+ await fixture.whenStable();
- expect(component.filteredGroups()![0].items).toEqual([fakeItem1]);
+ expect(component.filteredGroups()[0].items).toEqual([fakeItem1]);
});
- it('should display only class items when user selects Class in the Type select', () => {
+ it('should display items which match the query by group title', async () => {
+ fixture.componentRef.setInput('query', 'Fake Group');
+ await fixture.whenStable();
+
+ // Should find all items whose group title contains the query
+ expect(component.filteredGroups()[0].items.length).toBeGreaterThan(0);
+ expect(component.filteredGroups()[0].items).toContain(fakeItem1);
+ });
+
+ it('should display only class items when user selects Class in the Type select', async () => {
fixture.componentRef.setInput('type', ApiItemType.CLASS);
- fixture.detectChanges();
+ await fixture.whenStable();
- expect(component.type()).toEqual(ApiItemType.CLASS);
- expect(component.filteredGroups()![0].items).toEqual([fakeItem2]);
+ expect(component.form.type().value()).toEqual(ApiItemType.CLASS);
+ expect(component.filteredGroups()[0].items).toEqual([fakeItem2]);
});
it('should set selected type when provided type is different than selected', async () => {
- expect(component.type()).toBe(ALL_TYPES_KEY);
+ expect(component.form.type().value()).toBe(ALL_TYPES_KEY);
component.setItemType(ApiItemType.BLOCK);
await RouterTestingHarness.create(`/api?type=${ApiItemType.BLOCK}`);
- expect(component.type()).toBe(ApiItemType.BLOCK);
+ expect(component.form.type().value()).toBe(ApiItemType.BLOCK);
});
it('should reset selected type when provided type is equal to selected', async () => {
component.setItemType(ApiItemType.BLOCK);
const harness = await RouterTestingHarness.create(`/api?type=${ApiItemType.BLOCK}`);
- expect(component.type()).toBe(ApiItemType.BLOCK);
+ expect(component.form.type().value()).toBe(ApiItemType.BLOCK);
component.setItemType(ApiItemType.BLOCK);
harness.navigateByUrl(`/api`);
- expect(component.type()).toBe(ALL_TYPES_KEY);
+ expect(component.form.type().value()).toBe(ALL_TYPES_KEY);
});
it('should set the value of the queryParam equal to the query text field', async () => {
@@ -155,15 +164,15 @@ describe('ApiReferenceList', () => {
const location = TestBed.inject(Location);
const textField = fixture.debugElement.query(By.directive(TextField));
- (textField.componentInstance as TextField).setValue('item1');
+ fixture.componentInstance.form.query().value.set('item1');
await fixture.whenStable();
expect(location.path()).toBe(`?query=item1`);
- component.setItemType(ApiItemType.BLOCK);
+ fixture.componentInstance.form.type().value.set(ApiItemType.BLOCK);
await fixture.whenStable();
expect(location.path()).toBe(`?query=item1&type=${ApiItemType.BLOCK}`);
- fixture.componentRef.setInput('status', STATUSES.experimental);
+ fixture.componentInstance.form.status().value.set(STATUSES.experimental);
await fixture.whenStable();
expect(location.path()).toBe(
`?query=item1&type=${ApiItemType.BLOCK}&status=${STATUSES.experimental}`,
@@ -183,30 +192,30 @@ describe('ApiReferenceList', () => {
]);
});
- it('should not display deprecated and developer-preview and experimental items when status is set to stable', () => {
+ it('should not display deprecated and developer-preview and experimental items when status is set to stable', async () => {
fixture.componentRef.setInput('status', STATUSES.stable);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(component.filteredGroups()![0].items).toEqual([fakeItem1, fakeItem2]);
});
- it('should only display deprecated items when status is set to deprecated', () => {
+ it('should only display deprecated items when status is set to deprecated', async () => {
fixture.componentRef.setInput('status', STATUSES.deprecated);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(component.filteredGroups()![0].items).toEqual([fakeDeprecatedFeaturedItem]);
});
- it('should only display developer-preview items when status is set to developer-preview', () => {
+ it('should only display developer-preview items when status is set to developer-preview', async () => {
fixture.componentRef.setInput('status', STATUSES.developerPreview);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(component.filteredGroups()![0].items).toEqual([fakeDeveloperPreviewItem]);
});
- it('should only display experimental items when status is set to experimental', () => {
+ it('should only display experimental items when status is set to experimental', async () => {
fixture.componentRef.setInput('status', STATUSES.experimental);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(component.filteredGroups()![0].items).toEqual([fakeExperimentalItem]);
});
diff --git a/adev/src/app/features/references/api-reference-list/api-reference-list.component.ts b/adev/src/app/features/references/api-reference-list/api-reference-list.component.ts
index 5977d7c718e1..4df9cc877f59 100644
--- a/adev/src/app/features/references/api-reference-list/api-reference-list.component.ts
+++ b/adev/src/app/features/references/api-reference-list/api-reference-list.component.ts
@@ -6,33 +6,32 @@
* found in the LICENSE file at https://angular.dev/license
*/
+import {CdkMenuModule} from '@angular/cdk/menu';
+import {KeyValuePipe} from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
- ElementRef,
- computed,
- inject,
- linkedSignal,
- viewChild,
- afterNextRender,
EnvironmentInjector,
+ afterNextRender,
+ computed,
effect,
+ inject,
input,
+ linkedSignal,
} from '@angular/core';
-import ApiItemsSection from '../api-items-section/api-items-section.component';
-import {FormsModule} from '@angular/forms';
-import {TextField} from '@angular/docs';
-import {KeyValuePipe} from '@angular/common';
+import {Select, SelectOption, TextField} from '@angular/docs';
+import {FormField, form} from '@angular/forms/signals';
+import {MatChipListbox, MatChipOption} from '@angular/material/chips';
import {Params, Router} from '@angular/router';
-import {ApiItemType} from '../interfaces/api-item-type';
-import {ApiReferenceManager} from './api-reference-manager.service';
import ApiItemLabel from '../api-item-label/api-item-label.component';
-import {ApiLabel} from '../pipes/api-label.pipe';
+import ApiItemsSection from '../api-items-section/api-items-section.component';
+import {ApiItemType} from '../interfaces/api-item-type';
import {ApiItemsGroup} from '../interfaces/api-items-group';
-import {CdkMenuModule} from '@angular/cdk/menu';
-import {MatChipsModule} from '@angular/material/chips';
+import {ApiLabel} from '../pipes/api-label.pipe';
+import {ApiReferenceManager} from './api-reference-manager.service';
export const ALL_TYPES_KEY = 'All';
+export const ALL_PACKAGES = 'All';
export const STATUSES = {
stable: 1,
developerPreview: 2,
@@ -46,12 +45,14 @@ export const DEFAULT_STATUS = STATUSES.stable | STATUSES.developerPreview | STAT
imports: [
ApiItemsSection,
ApiItemLabel,
- FormsModule,
TextField,
ApiLabel,
CdkMenuModule,
- MatChipsModule,
+ MatChipListbox,
+ MatChipOption,
KeyValuePipe,
+ Select,
+ FormField,
],
templateUrl: './api-reference-list.component.html',
styleUrls: ['./api-reference-list.component.scss'],
@@ -61,19 +62,32 @@ export default class ApiReferenceList {
// services
private readonly apiReferenceManager = inject(ApiReferenceManager);
private readonly router = inject(Router);
- private readonly filterInput = viewChild.required(TextField, {read: ElementRef});
private readonly injector = inject(EnvironmentInjector);
// inputs
readonly queryInput = input
('', {alias: 'query'});
readonly typeInput = input(ALL_TYPES_KEY, {alias: 'type'});
readonly statusInput = input(DEFAULT_STATUS, {alias: 'status'});
+ protected selectedPackageInput = input(ALL_PACKAGES, {alias: 'package'});
// inputs are route binded, they can reset to undefined
// also we want a writable state, so we use a linked signal
- protected query = linkedSignal(() => this.queryInput() ?? '');
- public type = linkedSignal(() => this.typeInput() ?? ALL_TYPES_KEY);
- private status = linkedSignal(() => this.statusInput() ?? DEFAULT_STATUS);
+ public form = form(
+ linkedSignal(() => ({
+ query: this.queryInput() ?? '',
+ status: this.statusInput() ?? DEFAULT_STATUS,
+ type: this.typeInput() ?? ALL_TYPES_KEY,
+ selectedPackage: this.selectedPackageInput() ?? ALL_PACKAGES,
+ })),
+ );
+
+ protected packageOptions = computed(() => [
+ {label: 'All Packages', value: ALL_PACKAGES},
+ ...this.apiReferenceManager.apiGroups().map((group) => ({
+ label: group.title,
+ value: group.id,
+ })),
+ ]);
// const state
protected readonly itemTypes = Object.values(ApiItemType);
@@ -87,9 +101,9 @@ export default class ApiReferenceList {
};
readonly filteredGroups = computed((): ApiItemsGroup[] => {
- const query = this.query().toLocaleLowerCase();
- const status = this.status();
- const type = this.type();
+ const query = this.form.query().value().toLocaleLowerCase();
+ const status = this.form.status().value();
+ const type = this.form.type().value();
return this.apiReferenceManager
.apiGroups()
.map((group) => ({
@@ -97,7 +111,10 @@ export default class ApiReferenceList {
id: group.id,
items: group.items.filter((apiItem) => {
return (
- (query == '' ? true : apiItem.title.toLocaleLowerCase().includes(query)) &&
+ (query == ''
+ ? true
+ : apiItem.title.toLocaleLowerCase().includes(query) ||
+ group.title.toLocaleLowerCase().includes(query)) &&
(type === ALL_TYPES_KEY || apiItem.itemType === type) &&
((status & STATUSES.stable &&
!apiItem.developerPreview &&
@@ -109,19 +126,22 @@ export default class ApiReferenceList {
);
}),
}))
- .filter((group) => group.items.length > 0);
+ .filter(
+ (group) =>
+ group.items.length > 0 &&
+ (this.form.selectedPackage().value() === ALL_PACKAGES ||
+ group.id === this.form.selectedPackage().value()),
+ );
});
constructor() {
effect(() => {
- const filterInput = this.filterInput();
+ const filterInput = this.form.query();
afterNextRender(
{
write: () => {
- // Lord forgive me for I have sinned
- // Use the CVA to focus when https://github.com/angular/angular/issues/31133 is implemented
if (matchMedia('(hover: hover) and (pointer:fine)').matches) {
- scheduleOnIdle(() => filterInput.nativeElement.querySelector('input').focus());
+ scheduleOnIdle(() => filterInput.focusBoundControl());
}
},
},
@@ -132,9 +152,13 @@ export default class ApiReferenceList {
effect(() => {
// We'll only set the params if we deviate from the default values
const params: Params = {
- 'query': this.query() || null,
- 'type': this.type() === ALL_TYPES_KEY ? null : this.type(),
- 'status': this.status() === DEFAULT_STATUS ? null : this.status(),
+ 'query': this.form.query().value() || null,
+ 'type': this.form.type().value() === ALL_TYPES_KEY ? null : this.form.type().value(),
+ 'status': this.form.status().value() === DEFAULT_STATUS ? null : this.form.status().value(),
+ 'package':
+ this.form.selectedPackage().value() === ALL_PACKAGES
+ ? null
+ : this.form.selectedPackage().value(),
};
this.router.navigate([], {
@@ -149,11 +173,11 @@ export default class ApiReferenceList {
}
setItemType(itemType: ApiItemType): void {
- this.type.update((type) => (type === itemType ? ALL_TYPES_KEY : itemType));
+ this.form.type().value.update((type) => (type === itemType ? ALL_TYPES_KEY : itemType));
}
setStatus(status: number): void {
- this.status.update((previousStatus) => {
+ this.form.status().value.update((previousStatus) => {
if (this.isStatusSelected(status)) {
return previousStatus & ~status; // Clear the bit
} else {
@@ -163,7 +187,7 @@ export default class ApiReferenceList {
}
isStatusSelected(status: number): boolean {
- return (this.status() & status) === status;
+ return (this.form.status().value() & status) === status;
}
}
diff --git a/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.spec.ts b/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.spec.ts
index f5a4dab32054..753b7abc1ddd 100644
--- a/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.spec.ts
+++ b/adev/src/app/features/references/cli-reference-details-page/cli-reference-details-page.component.spec.ts
@@ -54,7 +54,7 @@ describe('CliReferenceDetailsPage', () => {
const harness = await RouterTestingHarness.create();
fixture = harness.fixture;
component = await harness.navigateByUrl('/', CliReferenceDetailsPage);
- fixture.detectChanges();
+ await fixture.whenStable();
});
it('should create', () => {
diff --git a/adev/src/app/features/references/interfaces/api-manifest.ts b/adev/src/app/features/references/interfaces/api-manifest.ts
index e422ea3cf8d2..aec3d834f956 100644
--- a/adev/src/app/features/references/interfaces/api-manifest.ts
+++ b/adev/src/app/features/references/interfaces/api-manifest.ts
@@ -10,6 +10,7 @@ import {ApiItemType} from './api-item-type';
export interface ApiManifestEntry {
name: string;
+ aliases?: string[];
type: ApiItemType;
category: string | undefined;
deprecated: {version: string | undefined} | undefined;
diff --git a/adev/src/app/features/tutorial/tutorial-navigation-list.ts b/adev/src/app/features/tutorial/tutorial-navigation-list.ts
index 68243567cca2..7ba2c882c4ae 100644
--- a/adev/src/app/features/tutorial/tutorial-navigation-list.ts
+++ b/adev/src/app/features/tutorial/tutorial-navigation-list.ts
@@ -15,29 +15,32 @@ import {NavigationItem} from '@angular/docs';
selector: 'adev-tutorial-navigation-list',
imports: [RouterLink, RouterLinkActive, NgTemplateOutlet],
template: `
-
-
- @for (item of navigationItems; track $index) {
-
-
- {{item.label}}
-
+
+
+ @for (item of navigationItems; track $index) {
+
+
+ {{ item.label }}
+
- @if (item.children && item.children.length > 0) {
-
+ @if (item.children && item.children.length > 0) {
+
+ }
+
}
-
- }
-
-
+
+
-
+
`,
styleUrls: ['./tutorial-navigation-list.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/adev/src/app/features/tutorial/tutorial-navigation.scss b/adev/src/app/features/tutorial/tutorial-navigation.scss
index 92067c70db37..2b8312b2101c 100644
--- a/adev/src/app/features/tutorial/tutorial-navigation.scss
+++ b/adev/src/app/features/tutorial/tutorial-navigation.scss
@@ -58,7 +58,7 @@
padding-block-end: calc(1.5rem + 50px);
margin-block-end: 1rem;
border-block-end: 1px solid var(--septenary-contrast);
- z-index: var(--z-index-nav);
+ z-index: var(--z-index-nav-tutorial);
transition: background-color 0.3s ease;
container: nav-container / inline-size;
diff --git a/adev/src/app/features/tutorial/tutorial.component.html b/adev/src/app/features/tutorial/tutorial.component.html
index d786c8fc23dd..1da8a56683f8 100644
--- a/adev/src/app/features/tutorial/tutorial.component.html
+++ b/adev/src/app/features/tutorial/tutorial.component.html
@@ -30,7 +30,7 @@
@if (embeddedEditorComponent) {
}
diff --git a/adev/src/app/features/tutorial/tutorial.component.spec.ts b/adev/src/app/features/tutorial/tutorial.component.spec.ts
index f679cb8ae860..90f7f51a5452 100644
--- a/adev/src/app/features/tutorial/tutorial.component.spec.ts
+++ b/adev/src/app/features/tutorial/tutorial.component.spec.ts
@@ -111,15 +111,13 @@ describe('Tutorial', () => {
},
});
- await TestBed;
-
fixture = TestBed.createComponent(Tutorial);
component = fixture.componentInstance;
// Replace EmbeddedEditor with FakeEmbeddedEditor
spyOn(component as any, 'loadEmbeddedEditorComponent').and.resolveTo(FakeEmbeddedEditor);
- fixture.detectChanges();
+ await fixture.whenStable();
});
it('should create', () => {
@@ -133,7 +131,7 @@ describe('Tutorial', () => {
it('should reset the reveal answer', async () => {
setupResetRevealAnswerValues();
- fixture.detectChanges();
+ await fixture.whenStable();
const revealAnswerButton = component.revealAnswerButton();
if (!revealAnswerButton) throw new Error('revealAnswerButton is undefined');
@@ -149,7 +147,7 @@ describe('Tutorial', () => {
it('should reveal the answer on button click', async () => {
setupRevealAnswerValues();
- fixture.detectChanges();
+ await fixture.whenStable();
const revealAnswerButton = component.revealAnswerButton();
if (!revealAnswerButton) throw new Error('revealAnswerButton is undefined');
@@ -165,14 +163,13 @@ describe('Tutorial', () => {
expect(embeddedTutorialManagerRevealAnswerSpy).toHaveBeenCalled();
await fixture.whenStable();
- fixture.detectChanges();
expect(revealAnswerButton.nativeElement.textContent?.trim()).toBe('Reset');
});
it('should not reveal the answer when button is disabled', async () => {
setupDisabledRevealAnswerValues();
- fixture.detectChanges();
+ await fixture.whenStable();
const revealAnswerButton = component.revealAnswerButton();
if (!revealAnswerButton) throw new Error('revealAnswerButton is undefined');
@@ -187,9 +184,9 @@ describe('Tutorial', () => {
expect(handleRevealAnswerSpy).not.toHaveBeenCalled();
});
- it('should not render the reveal answer button when there are no answers', () => {
+ it('should not render the reveal answer button when there are no answers', async () => {
setupNoRevealAnswerValues();
- fixture.detectChanges();
+ await fixture.whenStable();
expect(component.revealAnswerButton()).toBe(undefined);
});
diff --git a/adev/src/app/features/update/recommendations.ts b/adev/src/app/features/update/recommendations.ts
index 5b0f002d7057..36286a3c4b1d 100644
--- a/adev/src/app/features/update/recommendations.ts
+++ b/adev/src/app/features/update/recommendations.ts
@@ -208,7 +208,7 @@ export const RECOMMENDATIONS: Step[] = [
level: ApplicationComplexity.Basic,
step: 'Http',
action:
- "If you use the legacy `HttpModule` and the `Http` service, switch to `HttpClientModule` and the `HttpClient` service. HttpClient simplifies the default ergonomics (you don't need to map to JSON anymore) and now supports typed return values and interceptors. Read more on [angular.dev](https://angular.io/guide/http).",
+ "If you use the legacy `HttpModule` and the `Http` service, switch to `HttpClientModule` and the `HttpClient` service. HttpClient simplifies the default ergonomics (you don't need to map to JSON anymore) and now supports typed return values and interceptors. Read more on [angular.dev](https://angular.dev/guide/http).",
},
{
possibleIn: 430,
@@ -2604,7 +2604,8 @@ export const RECOMMENDATIONS: Step[] = [
necessaryAsOf: 2000,
level: ApplicationComplexity.Medium,
step: '20.0.0_rename_rxResource_loader_to_stream',
- action: 'Rename the `loader` property passed in rxResources to `stream`.',
+ action:
+ 'Rename the `request` and `loader` properties passed in RxResource to `params` and `stream`.',
},
{
possibleIn: 2000,
@@ -2750,4 +2751,165 @@ export const RECOMMENDATIONS: Step[] = [
action:
'Route configurations are now validated more rigorously. Routes that combine `redirectTo` and `canMatch` protections will generate an error, as these properties are incompatible together by default.',
},
+ {
+ action:
+ "In the application's project directory, run `ng update @angular/core@21 @angular/cli@21` to update your application to Angular v21.",
+ level: ApplicationComplexity.Basic,
+ necessaryAsOf: 2100,
+ possibleIn: 2100,
+ step: '21.0.0_ng_update',
+ },
+
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Basic,
+ material: true,
+ step: 'update @angular/material',
+ action: 'Run `ng update @angular/material@21`.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Advanced,
+ step: '21.0.0-update-signal-input-access-in-custom-elements',
+ action:
+ 'When using signal inputs with Angular custom elements, update property access to be direct (`elementRef.newInput`) instead of a function call (`elementRef.newInput()`) to align with the behavior of decorator-based inputs.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Advanced,
+ step: '21.0.0-zone-scheduler-behavior-change',
+ action:
+ "If using `provideZoneChangeDetection` without the ZoneJS polyfill, note that the internal scheduler is now always enabled. Review your app's timing as this may alter behavior that previously relied on the disabled scheduler.",
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Basic,
+ step: '21.0.0-provide-zone-change-detection-required',
+ action:
+ "Zone-based applications should add `provideZoneChangeDetection()` to your application's root providers. For standalone apps, add it to the `bootstrapApplication` call. For NgModule-based apps, add it to your root `AppModule`'s `providers` array. An automated migration should handle this.",
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Advanced,
+ step: '21.0.0-remove-interpolation-option',
+ action:
+ "Remove the 'interpolation' property from your @Component decorators. Angular now only supports the default '{{' and '}}' interpolation markers.",
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Medium,
+ step: '21.0.0-remove-moduleid-property',
+ action:
+ "Remove the 'moduleId' property from your @Component decorators. This property was used for resolving relative URLs for templates and styles, a functionality now handled by modern build tools.",
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Medium,
+ step: '21.0.0-ng-component-outlet-content-type-change',
+ action:
+ 'The `ngComponentOutletContent` input has been strictly typed from `any[][]` to `Node[][]`. Update the value you pass to this input to match the new `Node[][] | undefined` type.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Basic,
+ step: '21.0.0-stricter-host-binding-type-checking',
+ action:
+ "Host binding type checking is now enabled by default and may surface new build errors. Resolve any new type errors or set `typeCheckHostBindings: false` in your `tsconfig.json`'s `angularCompilerOptions`.",
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Basic,
+ step: '21.0.0-typescript-5.9-required',
+ action:
+ "Update your project's TypeScript version to 5.9 or later. The `ng update` command will typically handle this automatically.",
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Medium,
+ step: '21.0.0-remove-application-config-from-platform-browser',
+ action:
+ 'The `ApplicationConfig` export from `@angular/platform-browser` has been removed. Update your imports to use `ApplicationConfig` from `@angular/core` instead.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Advanced,
+ step: '21.0.0-remove-ignore-changes-outside-zone-option',
+ action:
+ 'The `ignoreChangesOutsideZone` option for configuring ZoneJS is no longer available. Remove this option from your ZoneJS configuration in your polyfills file.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Medium,
+ step: '21.0.0-testbed-rethrows-errors-with-provideZoneChangeDetection',
+ action:
+ 'Update tests using `provideZoneChangeDetection` as TestBed now rethrows errors. Fix the underlying issues in your tests or, as a last resort, configure TestBed with `rethrowApplicationErrors: false` to disable this behavior.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Medium,
+ step: '21.0.0-router-navigation-timing-changed',
+ action:
+ 'Update tests that rely on router navigation timing. Navigations may now take additional microtasks to complete. Ensure navigations are fully completed before making assertions, for example by using `fakeAsync` with `flush` or waiting for promises/observables to resolve.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Medium,
+ step: '21.0.0-test-bed-provides-fake-platform-location',
+ action:
+ 'Tests using `TestBed` might be affected by the new fake `PlatformLocation`. If your tests fail, provide the old `MockPlatformLocation` from `@angular/common/testing` via `{provide: PlatformLocation, useClass: MockPlatformLocation}` in your `TestBed` configuration.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Advanced,
+ step: '21.0.0-remove-upgrade-adapter',
+ action:
+ 'The `UpgradeAdapter` has been removed. Update your hybrid Angular/AngularJS application to use the static APIs from the `@angular/upgrade/static` package instead.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Medium,
+ step: '21.0.0-form-array-directive-conflict',
+ action:
+ 'The new standalone `formArray` directive might conflict with existing custom directives or inputs. Rename any custom directives named `FormArray` or inputs named `formArray` on elements that also use reactive forms to resolve the conflict.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Advanced,
+ step: '21.0.0-ngmodulefactory-removed',
+ action:
+ 'The deprecated `NgModuleFactory` has been removed. Update any code that uses `NgModuleFactory` to use `NgModule` directly, which is common in dynamic component loading scenarios.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Advanced,
+ step: '21.0.0-emit-declaration-only-not-supported',
+ action:
+ 'The `emitDeclarationOnly` TypeScript compiler option is not supported. Please disable it in your `tsconfig.json` file to allow the Angular compiler to function correctly.',
+ },
+ {
+ possibleIn: 2100,
+ necessaryAsOf: 2100,
+ level: ApplicationComplexity.Medium,
+ step: '21.0.0-lastsuccessfulnavigation-is-a-signal',
+ action:
+ 'The `lastSuccessfulNavigation` property on the Router has been converted to a signal. To get its value, you now need to invoke it as a function: `router.lastSuccessfulNavigation()`.',
+ },
];
diff --git a/adev/src/app/features/update/update.component.html b/adev/src/app/features/update/update.component.html
index 776ae55de6ee..5ebb3dcbb8c9 100644
--- a/adev/src/app/features/update/update.component.html
+++ b/adev/src/app/features/update/update.component.html
@@ -19,7 +19,7 @@ Angular versions
@for (version of versions; track $index) {
-
+
{{ version.name }}
@@ -40,7 +40,7 @@ Angular versions
@for (version of versions; track $index) {
-
+
{{ version.name }}
@@ -70,7 +70,7 @@ Angular versions
}
- @if ((to.number - from.number > 150) && from.number > 240) {
+ @if (to.number - from.number > 150 && from.number > 240) {
Warning:
@@ -86,7 +86,7 @@
Application complexity
Basic
Medium
@@ -106,7 +106,7 @@ Other dependencies
I use {{option.name}} {{option.description}} I use {{ option.name }} {{ option.description }}
}
@@ -137,16 +137,23 @@
Package Manager
@if (
- beforeRecommendations.length > 0 || duringRecommendations.length > 0 || afterRecommendations.length > 0
+ beforeRecommendations.length > 0 ||
+ duringRecommendations.length > 0 ||
+ afterRecommendations.length > 0
) {
-
{{title()}}
+
{{ title() }}
Before you update
- @for (r of beforeRecommendations; track $index) {
+ @for (r of beforeRecommendations; track $index) {
-
+
+
+
+ {{ getComplexityLevelName(r.level) }}
+
+
}
@if (beforeRecommendations.length <= 0) {
@@ -165,7 +172,12 @@
Update to the new version
@for (r of duringRecommendations; track $index) {
-
+
+
+
+ {{ getComplexityLevelName(r.level) }}
+
+
}
@if (duringRecommendations.length <= 0) {
@@ -178,7 +190,12 @@
After you update
@for (r of afterRecommendations; track $index) {
-
+
+
+
+ {{ getComplexityLevelName(r.level) }}
+
+
}
@if (afterRecommendations.length <= 0) {
diff --git a/adev/src/app/features/update/update.component.scss b/adev/src/app/features/update/update.component.scss
index e76b1e55562e..1cc5aa917bad 100644
--- a/adev/src/app/features/update/update.component.scss
+++ b/adev/src/app/features/update/update.component.scss
@@ -131,6 +131,42 @@ h4 {
margin-top: 0.5rem;
}
+ .adev-recommendation-content {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ }
+
+ .adev-complexity-badge {
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.25rem;
+ font-size: 0.75rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ white-space: nowrap;
+ flex-shrink: 0;
+
+ --badge-color: var(--primary-text, black);
+
+ color: var(--badge-color);
+ background: color-mix(in srgb, var(--badge-color) 10%, var(--page-background));
+
+ .docs-dark-mode & {
+ background: color-mix(in srgb, var(--badge-color) 17%, var(--page-background));
+ }
+
+ @include mq.for-tablet-landscape-down {
+ display: none;
+ }
+ }
+
+ .adev-complexity-1 { --badge-color: var(--super-green); }
+ .adev-complexity-2 { --badge-color: var(--bright-blue); }
+ .adev-complexity-3 { --badge-color: var(--symbolic-orange); }
+
// Code blocks are generable from the markdown, we need to opt-out of the scoping
::ng-deep code {
cursor: pointer;
diff --git a/adev/src/app/features/update/update.component.spec.ts b/adev/src/app/features/update/update.component.spec.ts
new file mode 100644
index 000000000000..c464fa41e4f0
--- /dev/null
+++ b/adev/src/app/features/update/update.component.spec.ts
@@ -0,0 +1,94 @@
+/*!
+ * @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 {ComponentFixture, TestBed} from '@angular/core/testing';
+import {provideRouter} from '@angular/router';
+import {provideHttpClient} from '@angular/common/http';
+import {provideHttpClientTesting} from '@angular/common/http/testing';
+import {By} from '@angular/platform-browser';
+
+import UpdateComponent from './update.component';
+import {ApplicationComplexity} from './recommendations';
+
+describe('UpdateComponent', () => {
+ let component: UpdateComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ imports: [UpdateComponent],
+ providers: [provideRouter([]), provideHttpClient(), provideHttpClientTesting()],
+ });
+ fixture = TestBed.createComponent(UpdateComponent);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe('getComplexityLevelName', () => {
+ it('should return "Basic" for ApplicationComplexity.Basic', () => {
+ expect(component['getComplexityLevelName'](ApplicationComplexity.Basic)).toBe('Basic');
+ });
+
+ it('should return "Medium" for ApplicationComplexity.Medium', () => {
+ expect(component['getComplexityLevelName'](ApplicationComplexity.Medium)).toBe('Medium');
+ });
+
+ it('should return "Advanced" for ApplicationComplexity.Advanced', () => {
+ expect(component['getComplexityLevelName'](ApplicationComplexity.Advanced)).toBe('Advanced');
+ });
+
+ it('should return "Unknown" for invalid complexity level', () => {
+ expect(component['getComplexityLevelName'](999 as ApplicationComplexity)).toBe('Unknown');
+ });
+ });
+
+ describe('complexity badges rendering', () => {
+ it('should display complexity badges for recommendations', async () => {
+ // Find the "Show me how to update!" button and click it to trigger recommendations
+ const showButton: HTMLElement = fixture.debugElement.query(
+ By.css('.show-button'),
+ )?.nativeElement;
+
+ expect(showButton).toBeTruthy();
+ showButton.click();
+
+ // Wait for all async operations to complete (marked parsing, router navigation, etc.)
+ await fixture.whenStable();
+
+ // Additional wait for marked parsing
+ await new Promise((resolve) => setTimeout(resolve, 300));
+
+ const badges = fixture.nativeElement.querySelectorAll('.adev-complexity-badge');
+
+ // For the default versions (20.0 -> 21.0) with level 1, verify badges are rendered
+ if (badges.length > 0) {
+ // Check the first badge
+ const badge = badges[0];
+ const badgeText = badge.textContent?.trim();
+
+ // Badge text should be one of the valid complexity levels
+ expect(['Basic', 'Medium', 'Advanced']).toContain(badgeText);
+
+ // Badge should have appropriate CSS class
+ const hasComplexityClass =
+ badge.classList.contains('adev-complexity-1') ||
+ badge.classList.contains('adev-complexity-2') ||
+ badge.classList.contains('adev-complexity-3');
+ expect(hasComplexityClass).toBe(true);
+ } else {
+ // If no recommendations exist for these versions, that's acceptable
+ // The test verifies the component renders without errors
+ expect(badges.length).toBe(0);
+ }
+ });
+ });
+});
diff --git a/adev/src/app/features/update/update.component.ts b/adev/src/app/features/update/update.component.ts
index 1537342506b0..29ddd1423dbe 100644
--- a/adev/src/app/features/update/update.component.ts
+++ b/adev/src/app/features/update/update.component.ts
@@ -7,14 +7,11 @@
*/
import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
-import {Step, RECOMMENDATIONS} from './recommendations';
+import {Step, RECOMMENDATIONS, ApplicationComplexity} from './recommendations';
import {Clipboard} from '@angular/cdk/clipboard';
-import {CdkMenuModule} from '@angular/cdk/menu';
-import {MatCheckboxModule} from '@angular/material/checkbox';
-import {MatInputModule} from '@angular/material/input';
-import {MatCardModule} from '@angular/material/card';
-import {MatGridListModule} from '@angular/material/grid-list';
-import {MatButtonToggleModule} from '@angular/material/button-toggle';
+import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu';
+import {MatCheckbox} from '@angular/material/checkbox';
+import {MatButtonToggleGroup, MatButtonToggle} from '@angular/material/button-toggle';
import {IconComponent} from '@angular/docs';
import {ActivatedRoute, Router} from '@angular/router';
import {marked} from 'marked';
@@ -33,12 +30,12 @@ const isWindows = typeof window !== 'undefined' && window.navigator.userAgent.in
templateUrl: './update.component.html',
styleUrl: './update.component.scss',
imports: [
- MatCheckboxModule,
- MatInputModule,
- MatCardModule,
- MatGridListModule,
- MatButtonToggleModule,
- CdkMenuModule,
+ MatCheckbox,
+ MatButtonToggleGroup,
+ MatButtonToggle,
+ CdkMenuTrigger,
+ CdkMenu,
+ CdkMenuItem,
IconComponent,
],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -71,6 +68,7 @@ export default class UpdateComponent {
protected afterRecommendations: Step[] = [];
protected readonly versions = [
+ {name: '21.0', number: 2100},
{name: '20.0', number: 2000},
{name: '19.0', number: 1900},
{name: '18.0', number: 1800},
@@ -108,8 +106,8 @@ export default class UpdateComponent {
{name: '2.1', number: 201},
{name: '2.0', number: 200},
];
- protected from = this.versions.find((version) => version.name === '19.0')!;
- protected to = this.versions.find((version) => version.name === '20.0')!;
+ protected from = this.versions.find((version) => version.name === '20.0')!;
+ protected to = this.versions.find((version) => version.name === '21.0')!;
protected futureVersion = 2100;
protected readonly steps: Step[] = RECOMMENDATIONS;
@@ -293,6 +291,16 @@ export default class UpdateComponent {
}
}
+ protected getComplexityLevelName(level: ApplicationComplexity): string {
+ const names: Record = {
+ [ApplicationComplexity.Basic]: 'Basic',
+ [ApplicationComplexity.Medium]: 'Medium',
+ [ApplicationComplexity.Advanced]: 'Advanced',
+ };
+
+ return names[level] ?? 'Unknown';
+ }
+
private replaceVariables(action: string): string {
let newAction = action;
newAction = newAction.replace(
diff --git a/adev/src/app/routing/navigation-entries/BUILD.bazel b/adev/src/app/routing/navigation-entries/BUILD.bazel
new file mode 100644
index 000000000000..b2538927cac5
--- /dev/null
+++ b/adev/src/app/routing/navigation-entries/BUILD.bazel
@@ -0,0 +1,33 @@
+load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
+
+package(default_visibility = ["//visibility:public"])
+
+ts_project(
+ name = "navigation-entries",
+ srcs = ["index.ts"],
+ declaration = True,
+ tsconfig = {
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "types": ["node"],
+ "rootDir": ".",
+ "skipLibCheck": True,
+ },
+ },
+ deps = [
+ "//adev:node_modules/@angular/compiler-cli",
+ "//adev:node_modules/@angular/core",
+ "//adev:node_modules/@angular/docs",
+ "//adev:node_modules/@types/node",
+ "//adev/src/content/reference/errors:route-nav-items",
+ "//adev/src/content/reference/extended-diagnostics:route-nav-items",
+ ],
+)
+
+filegroup(
+ name = "navigation_types",
+ srcs = [":navigation-entries"],
+ output_group = "types",
+)
diff --git a/adev/src/app/routing/navigation-entries/index.ts b/adev/src/app/routing/navigation-entries/index.ts
new file mode 100644
index 000000000000..0117ceb6eaae
--- /dev/null
+++ b/adev/src/app/routing/navigation-entries/index.ts
@@ -0,0 +1,1755 @@
+/*!
+ * @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: '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',
+ 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',
+ preserveOtherCategoryOrder: true,
+ 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',
+ status: 'new',
+ },
+ {
+ label: 'Form models',
+ path: 'guide/forms/signals/models',
+ contentPath: 'guide/forms/signals/models',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ label: 'Form model design',
+ path: 'guide/forms/signals/model-design',
+ contentPath: 'guide/forms/signals/designing-your-form-model',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ label: 'Field state management',
+ path: 'guide/forms/signals/field-state-management',
+ contentPath: 'guide/forms/signals/field-state-management',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ label: 'Validation',
+ path: 'guide/forms/signals/validation',
+ contentPath: 'guide/forms/signals/validation',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ label: 'Form logic',
+ path: 'guide/forms/signals/form-logic',
+ contentPath: 'guide/forms/signals/form-logic',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ label: 'Async operations',
+ path: 'guide/forms/signals/async-operations',
+ contentPath: 'guide/forms/signals/async-operations',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ label: 'Custom controls',
+ path: 'guide/forms/signals/custom-controls',
+ contentPath: 'guide/forms/signals/custom-controls',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ label: 'Comparison with other form systems',
+ path: 'guide/forms/signals/comparison',
+ contentPath: 'guide/forms/signals/comparison',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ label: 'Migrating from Reactive Forms',
+ path: 'guide/forms/signals/migration',
+ contentPath: 'guide/forms/signals/migration',
+ category: 'Signal Forms',
+ status: 'new',
+ },
+ {
+ 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',
+ },
+ {
+ 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',
+ 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',
+ preserveOtherCategoryOrder: true,
+ children: [
+ {
+ label: 'Overview',
+ path: 'best-practices/performance',
+ contentPath: 'best-practices/performance/overview',
+ },
+
+ // Loading Performance
+ {
+ label: 'Deferred loading with @defer',
+ path: 'best-practices/performance/defer',
+ contentPath: 'guide/templates/defer',
+ 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 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: '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',
+ 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',
+ },
+ ],
+ },
+];
+
+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/src/app/routing/redirections.ts b/adev/src/app/routing/redirections.ts
index 1eeba7d49d40..2bc0a3250ccc 100644
--- a/adev/src/app/routing/redirections.ts
+++ b/adev/src/app/routing/redirections.ts
@@ -95,25 +95,21 @@ export const REDIRECT_ROUTES: Route[] = [
path: 'guide/signals/inputs',
redirectTo: '/guide/components/inputs',
},
- {
- path: 'guide/ngmodules',
- redirectTo: '/guide/ngmodules/overview',
- },
{
path: 'guide/ngmodules/providers',
- redirectTo: '/guide/ngmodules/overview',
+ redirectTo: '/guide/ngmodules',
},
{
path: 'guide/ngmodules/singleton-services',
- redirectTo: '/guide/ngmodules/overview',
+ redirectTo: '/guide/ngmodules',
},
{
path: 'guide/ngmodules/lazy-loading',
- redirectTo: '/guide/ngmodules/overview',
+ redirectTo: '/guide/ngmodules',
},
{
path: 'guide/ngmodules/faq',
- redirectTo: '/guide/ngmodules/overview',
+ redirectTo: '/guide/ngmodules',
},
{
path: 'guide/components/anatomy-of-components',
@@ -164,4 +160,12 @@ export const REDIRECT_ROUTES: Route[] = [
path: 'guide/animations/reusable-animations',
redirectTo: '/guide/legacy-animations/reusable-animations',
},
+ {
+ path: 'guide/aria',
+ redirectTo: '/guide/aria/overview',
+ },
+ {
+ path: 'signal-forms',
+ redirectTo: '/playground?templateId=4-signal-forms',
+ },
];
diff --git a/adev/src/app/routing/router_providers.ts b/adev/src/app/routing/router_providers.ts
index 95340109ed42..4d02a6be731b 100644
--- a/adev/src/app/routing/router_providers.ts
+++ b/adev/src/app/routing/router_providers.ts
@@ -22,6 +22,7 @@ import {
RedirectCommand,
withNavigationErrorHandler,
withRouterConfig,
+ isActive,
} from '@angular/router';
import {routes} from './routes';
import {ADevTitleStrategy} from '../core/services/a-dev-title-strategy';
@@ -51,14 +52,14 @@ export const routerProviders = [
const router = inject(Router);
const toTree = createUrlTreeFromSnapshot(to, []);
// Skip the transition if the only thing changing is the fragment and queryParams
- if (
- router.isActive(toTree, {
- paths: 'exact',
- matrixParams: 'exact',
- fragment: 'ignored',
- queryParams: 'ignored',
- })
- ) {
+ const isTargetRouteCurrent = isActive(toTree, router, {
+ paths: 'exact',
+ matrixParams: 'exact',
+ fragment: 'ignored',
+ queryParams: 'ignored',
+ });
+
+ if (isTargetRouteCurrent()) {
transition.skipTransition();
}
},
diff --git a/adev/src/app/routing/routes.ts b/adev/src/app/routing/routes.ts
index 79758cbb555a..5d199e478991 100644
--- a/adev/src/app/routing/routes.ts
+++ b/adev/src/app/routing/routes.ts
@@ -45,7 +45,10 @@ const referencePageRoutes = mapNavigationItemsToRoutes(
const updateGuidePageRoute: Route = {
path: referenceNavigationItems.find((r) => r.path === DEFAULT_PAGES.UPDATE)!.path,
loadComponent: () => import('../features/update/update.component'),
- data: commonReferenceRouteData,
+ data: {
+ ...commonReferenceRouteData,
+ label: 'Update guide',
+ },
};
const cliReferencePageRoutes = mapNavigationItemsToRoutes(
@@ -155,7 +158,7 @@ export const routes: Route[] = [
// Error page
{
path: '**',
- loadComponent: () => import('../features/docs/docs.component'),
- resolve: {'docContent': contentResolver('error')},
+ loadComponent: () => import('../features/not-found/not-found').then((m) => m.NotFound),
+ data: {label: 'Page not found'},
},
];
diff --git a/adev/src/app/routing/sub-navigation-data.ts b/adev/src/app/routing/sub-navigation-data.ts
index 12d4d84abc3f..43f27a2578a4 100644
--- a/adev/src/app/routing/sub-navigation-data.ts
+++ b/adev/src/app/routing/sub-navigation-data.ts
@@ -1,4 +1,4 @@
-/*!
+/**
* @license
* Copyright Google LLC All Rights Reserved.
*
@@ -6,19 +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 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[];
@@ -27,1572 +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',
- },
- {
- 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',
- status: 'new',
- },
- {
- label: 'Form models',
- path: 'guide/forms/signals/models',
- contentPath: 'guide/forms/signals/models',
- status: 'new',
- },
- {
- label: 'Comparison with other form systems',
- path: 'guide/forms/signals/comparison',
- contentPath: 'guide/forms/signals/comparison',
- status: 'new',
- },
- ],
- },
- {
- 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: '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',
- },
- // 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,
- {
- 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/src/assets/BUILD.bazel b/adev/src/assets/BUILD.bazel
index c5754137afcd..49bf1976b449 100644
--- a/adev/src/assets/BUILD.bazel
+++ b/adev/src/assets/BUILD.bazel
@@ -10,9 +10,10 @@ copy_to_directory(
"//adev/src/content/ai",
"//adev/src/content/aria:aria_docs",
"//adev/src/content/best-practices",
+ "//adev/src/content/best-practices/performance",
"//adev/src/content/best-practices/runtime-performance",
"//adev/src/content/cdk:cdk_docs",
- "//adev/src/content/cli/help:cli_docs",
+ "//adev/src/content/cli:cli_docs",
"//adev/src/content/ecosystem",
"//adev/src/content/ecosystem/rxjs-interop",
"//adev/src/content/ecosystem/service-workers",
@@ -51,6 +52,8 @@ copy_to_directory(
"//adev/src/content/tutorials/deferrable-views:deferrable-views-guides",
"//adev/src/content/tutorials/first-app:first-app-guides",
"//adev/src/content/tutorials/learn-angular:learn-angular-guides",
+ "//adev/src/content/tutorials/signal-forms",
+ "//adev/src/content/tutorials/signal-forms:signal-forms-guides",
"//adev/src/content/tutorials/signals",
"//adev/src/content/tutorials/signals:signals-guides",
"//packages/animations:animations_docs",
@@ -92,7 +95,7 @@ copy_to_directory(
"//tools/manual_api_docs/elements:elements_docs",
],
replace_prefixes = {
- "adev/src/content/cli/help/cli_docs_html": "cli/",
+ "adev/src/content/cli/cli_docs_html": "cli/",
"adev/src/content/aria/aria_docs_html": "api/",
"adev/src/content/cdk/cdk_docs_html": "api/",
"adev/src/content": "",
@@ -109,6 +112,7 @@ copy_to_directory(
"//adev/src/content/tutorials/homepage",
"//adev/src/content/tutorials/learn-angular",
"//adev/src/content/tutorials/playground",
+ "//adev/src/content/tutorials/signal-forms",
"//adev/src/content/tutorials/signals",
],
replace_prefixes = {
diff --git a/adev/src/assets/images/angular-v21-hero.jpg b/adev/src/assets/images/angular-v21-hero.jpg
deleted file mode 100644
index fb0dc7e7bdef..000000000000
Binary files a/adev/src/assets/images/angular-v21-hero.jpg and /dev/null differ
diff --git a/adev/src/assets/images/guide/devtools/access-console.png b/adev/src/assets/images/guide/devtools/access-console.png
index 7380835dd925..33cf834d4090 100644
Binary files a/adev/src/assets/images/guide/devtools/access-console.png and b/adev/src/assets/images/guide/devtools/access-console.png differ
diff --git a/adev/src/assets/images/guide/devtools/component-explorer.png b/adev/src/assets/images/guide/devtools/component-explorer.png
index dae2add465fe..d503572bcfd6 100644
Binary files a/adev/src/assets/images/guide/devtools/component-explorer.png and b/adev/src/assets/images/guide/devtools/component-explorer.png differ
diff --git a/adev/src/assets/images/guide/devtools/default-profiler-view.png b/adev/src/assets/images/guide/devtools/default-profiler-view.png
index a1a882cbefa3..75b6e07701ad 100644
Binary files a/adev/src/assets/images/guide/devtools/default-profiler-view.png and b/adev/src/assets/images/guide/devtools/default-profiler-view.png differ
diff --git a/adev/src/assets/images/guide/devtools/defer-block.png b/adev/src/assets/images/guide/devtools/defer-block.png
new file mode 100644
index 000000000000..c6177bc65ba6
Binary files /dev/null and b/adev/src/assets/images/guide/devtools/defer-block.png differ
diff --git a/adev/src/assets/images/guide/devtools/devtools-tabs.png b/adev/src/assets/images/guide/devtools/devtools-tabs.png
index 844dc8910f94..c75fbaa71d83 100644
Binary files a/adev/src/assets/images/guide/devtools/devtools-tabs.png and b/adev/src/assets/images/guide/devtools/devtools-tabs.png differ
diff --git a/adev/src/assets/images/guide/devtools/devtools.png b/adev/src/assets/images/guide/devtools/devtools.png
index cbfddae977bd..c9fb46c4492f 100644
Binary files a/adev/src/assets/images/guide/devtools/devtools.png and b/adev/src/assets/images/guide/devtools/devtools.png differ
diff --git a/adev/src/assets/images/guide/devtools/di-injector-tree-providers.png b/adev/src/assets/images/guide/devtools/di-injector-tree-providers.png
index 794d413f3910..9bd16cbb9c53 100644
Binary files a/adev/src/assets/images/guide/devtools/di-injector-tree-providers.png and b/adev/src/assets/images/guide/devtools/di-injector-tree-providers.png differ
diff --git a/adev/src/assets/images/guide/devtools/di-injector-tree-selected.png b/adev/src/assets/images/guide/devtools/di-injector-tree-selected.png
index 6da211b5e6dc..a4af31f0833f 100644
Binary files a/adev/src/assets/images/guide/devtools/di-injector-tree-selected.png and b/adev/src/assets/images/guide/devtools/di-injector-tree-selected.png differ
diff --git a/adev/src/assets/images/guide/devtools/di-injector-tree.png b/adev/src/assets/images/guide/devtools/di-injector-tree.png
index dbcf522ec2d6..48a59730b9c8 100644
Binary files a/adev/src/assets/images/guide/devtools/di-injector-tree.png and b/adev/src/assets/images/guide/devtools/di-injector-tree.png differ
diff --git a/adev/src/assets/images/guide/devtools/hydration-overlay-ecom.png b/adev/src/assets/images/guide/devtools/hydration-overlay-ecom.png
new file mode 100644
index 000000000000..7fd16c41e039
Binary files /dev/null and b/adev/src/assets/images/guide/devtools/hydration-overlay-ecom.png differ
diff --git a/adev/src/assets/images/guide/devtools/hydration-status.png b/adev/src/assets/images/guide/devtools/hydration-status.png
new file mode 100644
index 000000000000..f07615eb7f9c
Binary files /dev/null and b/adev/src/assets/images/guide/devtools/hydration-status.png differ
diff --git a/adev/src/assets/images/guide/devtools/inspect-element.png b/adev/src/assets/images/guide/devtools/inspect-element.png
index bd91e0622fd4..627e49adce4d 100644
Binary files a/adev/src/assets/images/guide/devtools/inspect-element.png and b/adev/src/assets/images/guide/devtools/inspect-element.png differ
diff --git a/adev/src/assets/images/guide/devtools/navigate-source.png b/adev/src/assets/images/guide/devtools/navigate-source.png
index 7010019ba73e..f12043f29d5e 100644
Binary files a/adev/src/assets/images/guide/devtools/navigate-source.png and b/adev/src/assets/images/guide/devtools/navigate-source.png differ
diff --git a/adev/src/assets/images/guide/devtools/profiler-selected-bar.png b/adev/src/assets/images/guide/devtools/profiler-selected-bar.png
index 2235ed8a7f38..c32b5d7c48aa 100644
Binary files a/adev/src/assets/images/guide/devtools/profiler-selected-bar.png and b/adev/src/assets/images/guide/devtools/profiler-selected-bar.png differ
diff --git a/adev/src/assets/images/guide/devtools/profiler.png b/adev/src/assets/images/guide/devtools/profiler.png
index a320ff661261..ea79b64fcb6c 100644
Binary files a/adev/src/assets/images/guide/devtools/profiler.png and b/adev/src/assets/images/guide/devtools/profiler.png differ
diff --git a/adev/src/assets/images/guide/devtools/search.png b/adev/src/assets/images/guide/devtools/search.png
index 3eccdffde68e..eb6f1b62b745 100644
Binary files a/adev/src/assets/images/guide/devtools/search.png and b/adev/src/assets/images/guide/devtools/search.png differ
diff --git a/adev/src/assets/images/guide/devtools/show-hydration.png b/adev/src/assets/images/guide/devtools/show-hydration.png
new file mode 100644
index 000000000000..ba48cbfcc03c
Binary files /dev/null and b/adev/src/assets/images/guide/devtools/show-hydration.png differ
diff --git a/adev/src/assets/images/guide/devtools/update-property.png b/adev/src/assets/images/guide/devtools/update-property.png
index 82318598d09a..3ebe5a9d372a 100644
Binary files a/adev/src/assets/images/guide/devtools/update-property.png and b/adev/src/assets/images/guide/devtools/update-property.png differ
diff --git a/adev/src/assets/images/home/angular-logo-dark.svg b/adev/src/assets/images/home/angular-logo-dark.svg
new file mode 100644
index 000000000000..bfc701d76a19
--- /dev/null
+++ b/adev/src/assets/images/home/angular-logo-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/adev/src/assets/images/home/angular-logo-light.svg b/adev/src/assets/images/home/angular-logo-light.svg
new file mode 100644
index 000000000000..9c19178ae27f
--- /dev/null
+++ b/adev/src/assets/images/home/angular-logo-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/adev/src/assets/images/home/editor-dark.png b/adev/src/assets/images/home/editor-dark.png
new file mode 100644
index 000000000000..11c3962477e7
Binary files /dev/null and b/adev/src/assets/images/home/editor-dark.png differ
diff --git a/adev/src/assets/images/home/editor-light.png b/adev/src/assets/images/home/editor-light.png
new file mode 100644
index 000000000000..4850a6b65599
Binary files /dev/null and b/adev/src/assets/images/home/editor-light.png differ
diff --git a/adev/src/assets/images/v21-event/BUILD.bazel b/adev/src/assets/images/v21-event/BUILD.bazel
new file mode 100644
index 000000000000..9b89c177a55e
--- /dev/null
+++ b/adev/src/assets/images/v21-event/BUILD.bazel
@@ -0,0 +1,11 @@
+load("@aspect_rules_js//js:defs.bzl", "js_library")
+
+package(default_visibility = ["//visibility:public"])
+
+js_library(
+ name = "v21-event-images",
+ srcs = glob([
+ "*.png",
+ "*.jpg",
+ ]),
+)
diff --git a/adev/src/assets/images/v21-event/angular-v21-hero.jpg b/adev/src/assets/images/v21-event/angular-v21-hero.jpg
new file mode 100644
index 000000000000..247b88d643b1
Binary files /dev/null and b/adev/src/assets/images/v21-event/angular-v21-hero.jpg differ
diff --git a/adev/src/assets/images/v21-event/castle-sign.png b/adev/src/assets/images/v21-event/castle-sign.png
new file mode 100644
index 000000000000..b1e516714cf2
Binary files /dev/null and b/adev/src/assets/images/v21-event/castle-sign.png differ
diff --git a/adev/src/assets/images/v21-event/congrats-sign.png b/adev/src/assets/images/v21-event/congrats-sign.png
new file mode 100644
index 000000000000..4768dffe1256
Binary files /dev/null and b/adev/src/assets/images/v21-event/congrats-sign.png differ
diff --git a/adev/src/assets/images/v21-event/enter-sign.png b/adev/src/assets/images/v21-event/enter-sign.png
new file mode 100644
index 000000000000..6682fec7e7b9
Binary files /dev/null and b/adev/src/assets/images/v21-event/enter-sign.png differ
diff --git a/adev/src/assets/images/v21-event/entry-denied-sign.png b/adev/src/assets/images/v21-event/entry-denied-sign.png
new file mode 100644
index 000000000000..d0a8c07d3f60
Binary files /dev/null and b/adev/src/assets/images/v21-event/entry-denied-sign.png differ
diff --git a/adev/src/assets/images/v21-event/key.png b/adev/src/assets/images/v21-event/key.png
new file mode 100644
index 000000000000..dd83af609bfe
Binary files /dev/null and b/adev/src/assets/images/v21-event/key.png differ
diff --git a/adev/src/assets/images/v21-event/mascot-down-1.png b/adev/src/assets/images/v21-event/mascot-down-1.png
new file mode 100644
index 000000000000..76899638fcf8
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot-down-1.png differ
diff --git a/adev/src/assets/images/v21-event/mascot-down-2.png b/adev/src/assets/images/v21-event/mascot-down-2.png
new file mode 100644
index 000000000000..82f3ae41c6a4
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot-down-2.png differ
diff --git a/adev/src/assets/images/v21-event/mascot-left-1.png b/adev/src/assets/images/v21-event/mascot-left-1.png
new file mode 100644
index 000000000000..060551340b18
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot-left-1.png differ
diff --git a/adev/src/assets/images/v21-event/mascot-left-2.png b/adev/src/assets/images/v21-event/mascot-left-2.png
new file mode 100644
index 000000000000..b9ad270f8ced
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot-left-2.png differ
diff --git a/adev/src/assets/images/v21-event/mascot-right-1.png b/adev/src/assets/images/v21-event/mascot-right-1.png
new file mode 100644
index 000000000000..8a68248f6338
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot-right-1.png differ
diff --git a/adev/src/assets/images/v21-event/mascot-right-2.png b/adev/src/assets/images/v21-event/mascot-right-2.png
new file mode 100644
index 000000000000..fd574944b834
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot-right-2.png differ
diff --git a/adev/src/assets/images/v21-event/mascot-up-1.png b/adev/src/assets/images/v21-event/mascot-up-1.png
new file mode 100644
index 000000000000..9f01e0af93bc
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot-up-1.png differ
diff --git a/adev/src/assets/images/v21-event/mascot-up-2.png b/adev/src/assets/images/v21-event/mascot-up-2.png
new file mode 100644
index 000000000000..dde41ede5821
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot-up-2.png differ
diff --git a/adev/src/assets/images/v21-event/mascot.png b/adev/src/assets/images/v21-event/mascot.png
new file mode 100644
index 000000000000..05bff9ffebc1
Binary files /dev/null and b/adev/src/assets/images/v21-event/mascot.png differ
diff --git a/adev/src/assets/images/v21-event/welcome-sign.png b/adev/src/assets/images/v21-event/welcome-sign.png
new file mode 100644
index 000000000000..b582f466a549
Binary files /dev/null and b/adev/src/assets/images/v21-event/welcome-sign.png differ
diff --git a/adev/src/assets/images/v21-event/world-map.png b/adev/src/assets/images/v21-event/world-map.png
new file mode 100644
index 000000000000..5c18e9ea96a8
Binary files /dev/null and b/adev/src/assets/images/v21-event/world-map.png differ
diff --git a/adev/src/content/BUILD.bazel b/adev/src/content/BUILD.bazel
index 92a1848c877e..7b59635d52b1 100644
--- a/adev/src/content/BUILD.bazel
+++ b/adev/src/content/BUILD.bazel
@@ -7,7 +7,6 @@ generate_guides(
"error.md",
"kitchen-sink.md",
],
- api_manifest = "//adev/src/assets:docs_api_manifest",
data = [
"//adev/src/assets/images:components.svg",
"//adev/src/content/examples",
@@ -19,11 +18,15 @@ generate_guides(
js_library(
name = "guide_files",
srcs = [
+ "//adev/src/content/ai:guide_files",
"//adev/src/content/best-practices:guide_files",
"//adev/src/content/ecosystem:guide_files",
+ "//adev/src/content/events:guide_files",
"//adev/src/content/guide:guide_files",
"//adev/src/content/introduction:guide_files",
"//adev/src/content/reference:guide_files",
+ "//adev/src/content/tools:guide_files",
+ "//adev/src/content/tutorials:guide_files",
] + glob(["**/*.md"]),
visibility = ["//visibility:public"],
)
diff --git a/adev/src/content/ai/BUILD.bazel b/adev/src/content/ai/BUILD.bazel
index 0ed43c77fbe5..ee7fe691f65d 100644
--- a/adev/src/content/ai/BUILD.bazel
+++ b/adev/src/content/ai/BUILD.bazel
@@ -1,3 +1,4 @@
+load("//adev/shared-docs:defaults.bzl", "js_library")
load("//adev/shared-docs:index.bzl", "generate_guides")
generate_guides(
@@ -5,7 +6,6 @@ generate_guides(
srcs = glob([
"*.md",
]),
- api_manifest = "//adev/src/assets:docs_api_manifest",
data = [
"//adev/src/assets/images:what_is_angular.svg",
"//adev/src/context",
@@ -13,3 +13,9 @@ generate_guides(
],
visibility = ["//adev:__subpackages__"],
)
+
+js_library(
+ name = "guide_files",
+ srcs = glob(["**/*.md"]),
+ visibility = ["//adev/src/content:__pkg__"],
+)
diff --git a/adev/src/content/ai/ai-tutor.md b/adev/src/content/ai/ai-tutor.md
index fd3b01aed9b6..7c922be054e9 100644
--- a/adev/src/content/ai/ai-tutor.md
+++ b/adev/src/content/ai/ai-tutor.md
@@ -108,7 +108,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 +141,15 @@ 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: Experimental Signal Forms (⚠️ WARNING: Subject to Change)**
+
+**CRITICAL NOTE FOR THIS PHASE:** Signal Forms are currently an [**EXPERIMENTAL** feature](/reference/releases#experimental). The API may change significantly in future Angular releases. Please proceed with the understanding that this section demonstrates a cutting-edge feature.
+
+- **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/src/content/ai/design-patterns.md b/adev/src/content/ai/design-patterns.md
index cdcd7530d318..8a4ce4d670a6 100644
--- a/adev/src/content/ai/design-patterns.md
+++ b/adev/src/content/ai/design-patterns.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/src/content/ai/mcp-server-setup.md b/adev/src/content/ai/mcp-server-setup.md
index c4451d50a34a..1a9a8a6be5d4 100644
--- a/adev/src/content/ai/mcp-server-setup.md
+++ b/adev/src/content/ai/mcp-server-setup.md
@@ -12,16 +12,22 @@ The Angular CLI MCP server provides several tools to assist you in your developm
| `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. | ✅ | ✅ |
+| `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) | ✅ | ❌ |
+| Name | Description | `local-only` | `read-only` |
+| :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------: | :---------: |
+| `build` | Perform a one-off, non-watched build using `ng build`. | ✅ | ❌ |
+| `devserver.start` | Asynchronously starts a development server that watches the workspace for changes, similar to running `ng serve`. Since this is asynchronous it returns immediately. To manage the resulting server, use the `devserver.stop` and `devserver.wait_for_build` tools. | ✅ | ✅ |
+| `devserver.stop` | Stops a development server started by `devserver.start`. | ✅ | ✅ |
+| `devserver.wait_for_build` | Returns the output logs of the most recent build in a running development server started by `devserver.start`. If a build is currently ongoing, it will first wait for that build to complete and then return the logs. | ✅ | ✅ |
+| `e2e` | Executes the end-to-end tests configured in the project. | ✅ | ✅ |
+| `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) | ✅ | ❌ |
+| `test` | Runs the project's unit tests. | ✅ | ✅ |
## Get Started
@@ -129,11 +135,11 @@ For other IDEs, check your IDE's documentation for the proper location of the MC
The `mcp` command can be configured with the following options passed as arguments in your IDE's MCP configuration:
-| Option | Type | Description | Default |
-| :---------------------------- | :-------- | :-------------------------------------------------------------------------------------------------------------------------------- | :------ |
-| `--read-only` | `boolean` | Only register tools that do not make changes to the project. Your editor or coding agent may still perform edits. | `false` |
-| `--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`. | |
+| 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`. Enable all `devserver.x` tools by using `-E devserver`. | |
For example, to run the server in read-only mode in VS Code, you would update your `mcp.json` like this:
diff --git a/adev/src/content/ai/overview.md b/adev/src/content/ai/overview.md
index e2da24e3929b..b11f3e662363 100644
--- a/adev/src/content/ai/overview.md
+++ b/adev/src/content/ai/overview.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.
diff --git a/adev/src/content/aria/_build-info.json b/adev/src/content/aria/_build-info.json
index a8fd2bbd266f..d07891d00add 100644
--- a/adev/src/content/aria/_build-info.json
+++ b/adev/src/content/aria/_build-info.json
@@ -1,4 +1,4 @@
{
"branchName": "refs/heads/main",
- "sha": "149622e31586bd1101fab7d946f25c1feebe0279"
+ "sha": "a68740532edcf94dafd4a6862a8985f506718016"
}
\ No newline at end of file
diff --git a/adev/src/content/aria/aria-accordion.json b/adev/src/content/aria/aria-accordion.json
index c20aa8aecf84..1196c526c0d8 100755
--- a/adev/src/content/aria/aria-accordion.json
+++ b/adev/src/content/aria/aria-accordion.json
@@ -4,6 +4,31 @@
"moduleName": "@angular/aria/accordion",
"normalizedModuleName": "angular_aria_accordion",
"entries": [
+ {
+ "name": "AccordionContent",
+ "isAbstract": false,
+ "entryType": "undecorated_class",
+ "members": [],
+ "generics": [],
+ "description": "A structural directive that provides a mechanism for lazily rendering the content for an\n`ngAccordionPanel`.\n\nThis directive should be applied to an `ng-template` inside an `ngAccordionPanel`.\nIt allows the content of the panel to be lazily rendered, improving performance\nby only creating the content when the panel is first expanded.\n\n```html\n\n
\n This is the content that will be displayed inside the panel.
\n \n
\n```",
+ "jsdocTags": [
+ {
+ "name": "developerPreview",
+ "comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Accordion](guide/aria/accordion)"
+ }
+ ],
+ "rawComment": "/**\n * A structural directive that provides a mechanism for lazily rendering the content for an\n * `ngAccordionPanel`.\n *\n * This directive should be applied to an `ng-template` inside an `ngAccordionPanel`.\n * It allows the content of the panel to be lazily rendered, improving performance\n * by only creating the content when the panel is first expanded.\n *\n * ```html\n * \n *
\n * This is the content that will be displayed inside the panel.
\n * \n *
\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
+ "implements": [],
+ "source": {
+ "filePath": "src/aria/accordion/accordion-content.ts",
+ "startLine": 31,
+ "endLine": 35
+ }
+ },
{
"name": "AccordionPanel",
"isAbstract": false,
@@ -11,7 +36,7 @@
"members": [
{
"name": "id",
- "type": "any",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly"
@@ -21,7 +46,7 @@
},
{
"name": "panelId",
- "type": "any",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly"
@@ -31,7 +56,7 @@
},
{
"name": "visible",
- "type": "any",
+ "type": "Signal",
"memberType": "property",
"memberTags": [
"readonly"
@@ -145,14 +170,18 @@
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Accordion](guide/aria/accordion)"
}
],
- "rawComment": "/**\n * The content panel of an accordion item that is conditionally visible.\n *\n * This directive is a container for the content that is shown or hidden. It requires\n * a `panelId` that must match the `panelId` of its corresponding `ngAccordionTrigger`.\n * The content within the panel should be provided using an `ng-template` with the\n * `ngAccordionContent` directive so that the content is not rendered on the page until the trigger\n * is expanded. It applies `role=\"region\"` for accessibility and uses the `inert` attribute to hide\n * its content from assistive technologies when not visible.\n *\n * ```html\n * \n *
\n * This content is lazily rendered and will be shown when the panel is expanded.
\n * \n *
\n * ```\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * The content panel of an accordion item that is conditionally visible.\n *\n * This directive is a container for the content that is shown or hidden. It requires\n * a `panelId` that must match the `panelId` of its corresponding `ngAccordionTrigger`.\n * The content within the panel should be provided using an `ng-template` with the\n * `ngAccordionContent` directive so that the content is not rendered on the page until the trigger\n * is expanded. It applies `role=\"region\"` for accessibility and uses the `inert` attribute to hide\n * its content from assistive technologies when not visible.\n *\n * ```html\n * \n *
\n * This content is lazily rendered and will be shown when the panel is expanded.
\n * \n *
\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
"implements": [],
"source": {
- "filePath": "src/aria/accordion/accordion.ts",
- "startLine": 52,
- "endLine": 113
+ "filePath": "/src/aria/accordion/accordion-panel.ts",
+ "startLine": 42,
+ "endLine": 103
}
},
{
@@ -172,7 +201,7 @@
},
{
"name": "id",
- "type": "any",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly",
@@ -185,7 +214,7 @@
},
{
"name": "panelId",
- "type": "any",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly",
@@ -198,7 +227,7 @@
},
{
"name": "disabled",
- "type": "any",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
@@ -211,7 +240,7 @@
},
{
"name": "expanded",
- "type": "any",
+ "type": "ModelSignal",
"memberType": "property",
"memberTags": [
"readonly",
@@ -226,7 +255,7 @@
},
{
"name": "active",
- "type": "any",
+ "type": "Signal",
"memberType": "property",
"memberTags": [
"readonly"
@@ -340,19 +369,26 @@
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Accordion](guide/aria/accordion)"
}
],
- "rawComment": "/**\n * The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n *\n * This directive requires a `panelId` that must match the `panelId` of the `ngAccordionPanel` it\n * controls. When clicked, it will expand or collapse the panel. It also handles keyboard\n * interactions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n * `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\n * The `disabled` input can be used to disable the trigger.\n *\n * ```html\n * \n * Accordion Trigger Text\n * \n * ```\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n *\n * This directive requires a `panelId` that must match the `panelId` of the `ngAccordionPanel` it\n * controls. When clicked, it will expand or collapse the panel. It also handles keyboard\n * interactions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n * `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\n * The `disabled` input can be used to disable the trigger.\n *\n * ```html\n * \n * Accordion Trigger Text\n * \n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
"implements": [],
"isStandalone": true,
"selector": "[ngAccordionTrigger]",
"exportAs": [
"ngAccordionTrigger"
],
+ "aliases": [
+ "ngAccordionTrigger"
+ ],
"source": {
- "filePath": "src/aria/accordion/accordion.ts",
- "startLine": 132,
- "endLine": 197
+ "filePath": "/src/aria/accordion/accordion-trigger.ts",
+ "startLine": 42,
+ "endLine": 107
}
},
{
@@ -372,7 +408,7 @@
},
{
"name": "textDirection",
- "type": "any",
+ "type": "WritableSignal",
"memberType": "property",
"memberTags": [
"readonly"
@@ -382,7 +418,7 @@
},
{
"name": "disabled",
- "type": "any",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
@@ -395,7 +431,7 @@
},
{
"name": "multiExpandable",
- "type": "any",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
@@ -408,7 +444,7 @@
},
{
"name": "softDisabled",
- "type": "any",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
@@ -421,7 +457,7 @@
},
{
"name": "wrap",
- "type": "any",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
@@ -505,40 +541,26 @@
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Accordion](guide/aria/accordion)"
}
],
- "rawComment": "/**\n * A container for a group of accordion items. It manages the overall state and\n * interactions of the accordion, such as keyboard navigation and expansion mode.\n *\n * The `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\n * coordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\n * It supports both single and multiple expansion modes.\n *\n * ```html\n * \n *
\n *
\n * Item 1 \n * \n *
\n *
\n * Content for Item 1.
\n * \n *
\n *
\n *
\n *
\n * Item 2 \n * \n *
\n *
\n * Content for Item 2.
\n * \n *
\n *
\n *
\n * ```\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * A container for a group of accordion items. It manages the overall state and\n * interactions of the accordion, such as keyboard navigation and expansion mode.\n *\n * The `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\n * coordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\n * It supports both single and multiple expansion modes.\n *\n * ```html\n * \n *
\n *
\n * Item 1 \n * \n *
\n *
\n * Content for Item 1.
\n * \n *
\n *
\n *
\n *
\n * Item 2 \n * \n *
\n *
\n * Content for Item 2.
\n * \n *
\n *
\n *
\n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */",
"implements": [],
"isStandalone": true,
"selector": "[ngAccordionGroup]",
"exportAs": [
"ngAccordionGroup"
],
- "source": {
- "filePath": "src/aria/accordion/accordion.ts",
- "startLine": 234,
- "endLine": 329
- }
- },
- {
- "name": "AccordionContent",
- "isAbstract": false,
- "entryType": "undecorated_class",
- "members": [],
- "generics": [],
- "description": "A structural directive that provides a mechanism for lazily rendering the content for an\n`ngAccordionPanel`.\n\nThis directive should be applied to an `ng-template` inside an `ngAccordionPanel`.\nIt allows the content of the panel to be lazily rendered, improving performance\nby only creating the content when the panel is first expanded.\n\n```html\n\n
\n This is the content that will be displayed inside the panel.
\n \n
\n```",
- "jsdocTags": [
- {
- "name": "developerPreview",
- "comment": "21.0"
- }
+ "aliases": [
+ "ngAccordionGroup"
],
- "rawComment": "/**\n * A structural directive that provides a mechanism for lazily rendering the content for an\n * `ngAccordionPanel`.\n *\n * This directive should be applied to an `ng-template` inside an `ngAccordionPanel`.\n * It allows the content of the panel to be lazily rendered, improving performance\n * by only creating the content when the panel is first expanded.\n *\n * ```html\n * \n *
\n * This is the content that will be displayed inside the panel.
\n * \n *
\n * ```\n *\n * @developerPreview 21.0\n */",
- "implements": [],
"source": {
- "filePath": "src/aria/accordion/accordion.ts",
- "startLine": 349,
- "endLine": 353
+ "filePath": "src/aria/accordion/accordion-group.ts",
+ "startLine": 62,
+ "endLine": 158
}
}
],
@@ -551,18 +573,10 @@
"input",
"@angular/core"
],
- [
- "ElementRef",
- "@angular/core"
- ],
[
"inject",
"@angular/core"
],
- [
- "contentChildren",
- "@angular/core"
- ],
[
"afterRenderEffect",
"@angular/core"
@@ -571,14 +585,6 @@
"signal",
"@angular/core"
],
- [
- "model",
- "@angular/core"
- ],
- [
- "booleanAttribute",
- "@angular/core"
- ],
[
"computed",
"@angular/core"
@@ -592,28 +598,28 @@
"@angular/cdk/a11y"
],
[
- "Directionality",
- "@angular/cdk/bidi"
+ "ElementRef",
+ "@angular/core"
],
[
- "DeferredContent",
- "@angular/aria/private"
+ "model",
+ "@angular/core"
],
[
- "DeferredContentAware",
- "@angular/aria/private"
+ "booleanAttribute",
+ "@angular/core"
],
[
- "AccordionGroupPattern",
- "@angular/aria/private"
+ "contentChildren",
+ "@angular/core"
],
[
- "AccordionPanelPattern",
- "@angular/aria/private"
+ "Directionality",
+ "@angular/cdk/bidi"
],
[
- "AccordionTriggerPattern",
- "@angular/aria/private"
+ "AccordionContent",
+ "@angular/aria/accordion"
],
[
"AccordionPanel",
@@ -734,10 +740,6 @@
[
"AccordionGroup.collapseAll",
"@angular/aria/accordion"
- ],
- [
- "AccordionContent",
- "@angular/aria/accordion"
]
]
}
\ No newline at end of file
diff --git a/adev/src/content/aria/aria-combobox.json b/adev/src/content/aria/aria-combobox.json
index 1ac7e96b4978..8f720d3dc9b2 100755
--- a/adev/src/content/aria/aria-combobox.json
+++ b/adev/src/content/aria/aria-combobox.json
@@ -5,153 +5,79 @@
"normalizedModuleName": "angular_aria_combobox",
"entries": [
{
- "name": "Combobox",
+ "name": "ComboboxPopupContainer",
"isAbstract": false,
"entryType": "undecorated_class",
- "members": [
- {
- "name": "textDirection",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "protected"
- ],
- "description": "A signal wrapper for directionality.",
- "jsdocTags": []
- },
- {
- "name": "element",
- "type": "HTMLElement",
- "memberType": "property",
- "memberTags": [
- "readonly"
- ],
- "description": "A reference to the combobox element.",
- "jsdocTags": []
- },
+ "members": [],
+ "generics": [],
+ "description": "A structural directive that marks the `ng-template` to be used as the popup\nfor a combobox. This content is conditionally rendered.\n\nThe content of the popup can be a `ngListbox`, `ngTree`, or `role=\"dialog\"`, allowing for\nflexible and complex combobox implementations. The consumer is responsible for\nimplementing the filtering logic based on the `ngComboboxInput`'s value.\n\n```html\n\n \n \n
\n \n```\n\nWhen using CdkOverlay, this directive can be replaced by `cdkConnectedOverlay`.\n\n```html\n\n \n \n
\n \n```",
+ "jsdocTags": [
{
- "name": "popup",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly"
- ],
- "description": "The combobox popup.",
- "jsdocTags": []
+ "name": "developerPreview",
+ "comment": "21.0"
},
{
- "name": "filterMode",
- "type": "any",
- "memberType": "property",
- "memberTags": [],
- "description": "The filter mode for the combobox.\n- `manual`: The consumer is responsible for filtering the options.\n- `auto-select`: The combobox automatically selects the first matching option.\n- `highlight`: The combobox highlights matching text in the options without changing selection.",
- "jsdocTags": []
+ "name": "see",
+ "comment": "[Combobox](guide/aria/combobox)"
},
{
- "name": "disabled",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly"
- ],
- "description": "Whether the combobox is disabled.",
- "jsdocTags": []
+ "name": "see",
+ "comment": "[Select](guide/aria/select)"
},
{
- "name": "readonly",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly"
- ],
- "description": "Whether the combobox is read-only.",
- "jsdocTags": []
+ "name": "see",
+ "comment": "[Multiselect](guide/aria/multiselect)"
},
{
- "name": "firstMatch",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly"
- ],
- "description": "The value of the first matching item in the popup.",
- "jsdocTags": []
- },
- {
- "name": "expanded",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly"
- ],
- "description": "Whether the combobox is expanded.",
- "jsdocTags": []
- },
+ "name": "see",
+ "comment": "[Autocomplete](guide/aria/autocomplete)"
+ }
+ ],
+ "rawComment": "/**\n * A structural directive that marks the `ng-template` to be used as the popup\n * for a combobox. This content is conditionally rendered.\n *\n * The content of the popup can be a `ngListbox`, `ngTree`, or `role=\"dialog\"`, allowing for\n * flexible and complex combobox implementations. The consumer is responsible for\n * implementing the filtering logic based on the `ngComboboxInput`'s value.\n *\n * ```html\n * \n * \n * \n *
\n * \n * ```\n *\n * When using CdkOverlay, this directive can be replaced by `cdkConnectedOverlay`.\n *\n * ```html\n * \n * \n * \n *
\n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */",
+ "implements": [],
+ "source": {
+ "filePath": "src/aria/combobox/combobox-popup-container.ts",
+ "startLine": 47,
+ "endLine": 52
+ }
+ },
+ {
+ "name": "ComboboxDialog",
+ "isAbstract": false,
+ "entryType": "directive",
+ "members": [
{
- "name": "alwaysExpanded",
- "type": "any",
+ "name": "element",
+ "type": "HTMLElement",
"memberType": "property",
"memberTags": [
"readonly"
],
- "description": "Whether the combobox popup should always be expanded, regardless of user interaction.",
+ "description": "A reference to the dialog element.",
"jsdocTags": []
},
{
- "name": "inputElement",
- "type": "any",
+ "name": "combobox",
+ "type": "Combobox",
"memberType": "property",
"memberTags": [
"readonly"
],
- "description": "Input element connected to the combobox, if any.",
+ "description": "The combobox that the dialog belongs to.",
"jsdocTags": []
},
- {
- "name": "open",
- "signatures": [
- {
- "name": "open",
- "entryType": "function",
- "description": "Opens the combobox to the selected item.",
- "generics": [],
- "isNewType": false,
- "jsdocTags": [],
- "params": [],
- "rawComment": "/** Opens the combobox to the selected item. */",
- "returnType": "void"
- }
- ],
- "implementation": {
- "params": [],
- "isNewType": false,
- "returnType": "void",
- "generics": [],
- "name": "open",
- "description": "Opens the combobox to the selected item.",
- "entryType": "function",
- "jsdocTags": [],
- "rawComment": "/** Opens the combobox to the selected item. */"
- },
- "entryType": "function",
- "description": "Opens the combobox to the selected item.",
- "jsdocTags": [],
- "rawComment": "/** Opens the combobox to the selected item. */",
- "memberType": "method",
- "memberTags": []
- },
{
"name": "close",
"signatures": [
{
"name": "close",
"entryType": "function",
- "description": "Closes the combobox.",
+ "description": "",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
- "rawComment": "/** Closes the combobox. */",
+ "rawComment": "",
"returnType": "void"
}
],
@@ -161,41 +87,57 @@
"returnType": "void",
"generics": [],
"name": "close",
- "description": "Closes the combobox.",
+ "description": "",
"entryType": "function",
"jsdocTags": [],
- "rawComment": "/** Closes the combobox. */"
+ "rawComment": ""
},
"entryType": "function",
- "description": "Closes the combobox.",
+ "description": "",
"jsdocTags": [],
- "rawComment": "/** Closes the combobox. */",
+ "rawComment": "",
"memberType": "method",
"memberTags": []
}
],
- "generics": [
- {
- "name": "V"
- }
- ],
- "description": "The container element that wraps a combobox input and popup, and orchestrates its behavior.\n\nThe `ngCombobox` directive is the main entry point for creating a combobox and customizing its\nbehavior. It coordinates the interactions between the `ngComboboxInput` and the popup, which\nis defined by a `ng-template` with the `ngComboboxPopupContainer` directive. If using the\n`CdkOverlay`, the `cdkConnectedOverlay` directive takes the place of `ngComboboxPopupContainer`.\n\n```html\n\n
\n\n
\n ",
+ "generics": [],
+ "description": "Integrates a native `
` element with the combobox, allowing for\na modal or non-modal popup experience. It handles the opening and closing of the dialog\nbased on the combobox's expanded state.\n\n```html\n\n \n \n \n \n```",
"jsdocTags": [
- {
- "name": "for",
- "comment": "(option of filteredOptions(); track option) {\n\n{{option}} \n
\n}\n \n \n
\n```"
- },
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Combobox](guide/aria/combobox)"
+ },
+ {
+ "name": "see",
+ "comment": "[Select](guide/aria/select)"
+ },
+ {
+ "name": "see",
+ "comment": "[Multiselect](guide/aria/multiselect)"
+ },
+ {
+ "name": "see",
+ "comment": "[Autocomplete](guide/aria/autocomplete)"
}
],
- "rawComment": "/**\n * The container element that wraps a combobox input and popup, and orchestrates its behavior.\n *\n * The `ngCombobox` directive is the main entry point for creating a combobox and customizing its\n * behavior. It coordinates the interactions between the `ngComboboxInput` and the popup, which\n * is defined by a `ng-template` with the `ngComboboxPopupContainer` directive. If using the\n * `CdkOverlay`, the `cdkConnectedOverlay` directive takes the place of `ngComboboxPopupContainer`.\n *\n * ```html\n * \n *
\n *\n *
\n * \n * @for (option of filteredOptions(); track option) {\n *
\n * {{option}} \n *
\n * }\n *
\n * \n *
\n * ```\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * Integrates a native `` element with the combobox, allowing for\n * a modal or non-modal popup experience. It handles the opening and closing of the dialog\n * based on the combobox's expanded state.\n *\n * ```html\n * \n * \n * \n * \n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */",
"implements": [],
+ "isStandalone": true,
+ "selector": "dialog[ngComboboxDialog]",
+ "exportAs": [
+ "ngComboboxDialog"
+ ],
+ "aliases": [
+ "ngComboboxDialog"
+ ],
"source": {
- "filePath": "src/aria/combobox/combobox.ts",
- "startLine": 64,
- "endLine": 170
+ "filePath": "src/aria/combobox/combobox-dialog.ts",
+ "startLine": 34,
+ "endLine": 84
}
},
{
@@ -215,7 +157,7 @@
},
{
"name": "combobox",
- "type": "any",
+ "type": "Combobox",
"memberType": "property",
"memberTags": [
"readonly"
@@ -225,7 +167,7 @@
},
{
"name": "value",
- "type": "any",
+ "type": "ModelSignal",
"memberType": "property",
"memberTags": [
"input",
@@ -244,40 +186,38 @@
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Combobox](guide/aria/combobox)"
+ },
+ {
+ "name": "see",
+ "comment": "[Select](guide/aria/select)"
+ },
+ {
+ "name": "see",
+ "comment": "[Multiselect](guide/aria/multiselect)"
+ },
+ {
+ "name": "see",
+ "comment": "[Autocomplete](guide/aria/autocomplete)"
}
],
- "rawComment": "/**\n * An input that is part of a combobox. It is responsible for displaying the\n * current value and handling user input for filtering and selection.\n *\n * This directive should be applied to an ` ` element within an `ngCombobox`\n * container. It automatically handles keyboard interactions, such as opening the\n * popup and navigating through the options.\n *\n * ```html\n * \n * ```\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * An input that is part of a combobox. It is responsible for displaying the\n * current value and handling user input for filtering and selection.\n *\n * This directive should be applied to an ` ` element within an `ngCombobox`\n * container. It automatically handles keyboard interactions, such as opening the\n * popup and navigating through the options.\n *\n * ```html\n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */",
"implements": [],
"isStandalone": true,
"selector": "input[ngComboboxInput]",
"exportAs": [
"ngComboboxInput"
],
- "source": {
- "filePath": "src/aria/combobox/combobox.ts",
- "startLine": 190,
- "endLine": 236
- }
- },
- {
- "name": "ComboboxPopupContainer",
- "isAbstract": false,
- "entryType": "undecorated_class",
- "members": [],
- "generics": [],
- "description": "A structural directive that marks the `ng-template` to be used as the popup\nfor a combobox. This content is conditionally rendered.\n\nThe content of the popup can be a `ngListbox`, `ngTree`, or `role=\"dialog\"`, allowing for\nflexible and complex combobox implementations. The consumer is responsible for\nimplementing the filtering logic based on the `ngComboboxInput`'s value.\n\n```html\n\n \n \n
\n \n```\n\nWhen using CdkOverlay, this directive can be replaced by `cdkConnectedOverlay`.\n\n```html\n\n \n \n
\n \n```",
- "jsdocTags": [
- {
- "name": "developerPreview",
- "comment": "21.0"
- }
+ "aliases": [
+ "ngComboboxInput"
],
- "rawComment": "/**\n * A structural directive that marks the `ng-template` to be used as the popup\n * for a combobox. This content is conditionally rendered.\n *\n * The content of the popup can be a `ngListbox`, `ngTree`, or `role=\"dialog\"`, allowing for\n * flexible and complex combobox implementations. The consumer is responsible for\n * implementing the filtering logic based on the `ngComboboxInput`'s value.\n *\n * ```html\n * \n * \n * \n *
\n * \n * ```\n *\n * When using CdkOverlay, this directive can be replaced by `cdkConnectedOverlay`.\n *\n * ```html\n * \n * \n * \n *
\n * \n * ```\n *\n * @developerPreview 21.0\n */",
- "implements": [],
"source": {
- "filePath": "src/aria/combobox/combobox.ts",
- "startLine": 268,
- "endLine": 273
+ "filePath": "src/aria/combobox/combobox-input.ts",
+ "startLine": 44,
+ "endLine": 90
}
},
{
@@ -287,7 +227,7 @@
"members": [
{
"name": "combobox",
- "type": "any",
+ "type": "Combobox | null",
"memberType": "property",
"memberTags": [
"readonly"
@@ -306,26 +246,55 @@
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Combobox](guide/aria/combobox)"
+ },
+ {
+ "name": "see",
+ "comment": "[Select](guide/aria/select)"
+ },
+ {
+ "name": "see",
+ "comment": "[Multiselect](guide/aria/multiselect)"
+ },
+ {
+ "name": "see",
+ "comment": "[Autocomplete](guide/aria/autocomplete)"
}
],
- "rawComment": "/**\n * Identifies an element as a popup for an `ngCombobox`.\n *\n * This directive acts as a bridge, allowing the `ngCombobox` to discover and interact\n * with the underlying control (e.g., `ngListbox`, `ngTree`, or `ngComboboxDialog`) that\n * manages the options. It's primarily used as a host directive and is responsible for\n * exposing the popup's control pattern to the parent combobox.\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * Identifies an element as a popup for an `ngCombobox`.\n *\n * This directive acts as a bridge, allowing the `ngCombobox` to discover and interact\n * with the underlying control (e.g., `ngListbox`, `ngTree`, or `ngComboboxDialog`) that\n * manages the options. It's primarily used as a host directive and is responsible for\n * exposing the popup's control pattern to the parent combobox.\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */",
"implements": [],
"isStandalone": true,
"selector": "[ngComboboxPopup]",
"exportAs": [
"ngComboboxPopup"
],
+ "aliases": [
+ "ngComboboxPopup"
+ ],
"source": {
- "filePath": "src/aria/combobox/combobox.ts",
- "startLine": 285,
- "endLine": 300
+ "filePath": "/src/aria/combobox/combobox-popup.ts",
+ "startLine": 29,
+ "endLine": 44
}
},
{
- "name": "ComboboxDialog",
+ "name": "Combobox",
"isAbstract": false,
- "entryType": "directive",
+ "entryType": "undecorated_class",
"members": [
+ {
+ "name": "textDirection",
+ "type": "Signal",
+ "memberType": "property",
+ "memberTags": [
+ "protected"
+ ],
+ "description": "A signal wrapper for directionality.",
+ "jsdocTags": []
+ },
{
"name": "element",
"type": "HTMLElement",
@@ -333,31 +302,132 @@
"memberTags": [
"readonly"
],
- "description": "A reference to the dialog element.",
+ "description": "A reference to the combobox element.",
"jsdocTags": []
},
{
- "name": "combobox",
- "type": "any",
+ "name": "popup",
+ "type": "Signal | undefined>",
"memberType": "property",
"memberTags": [
"readonly"
],
- "description": "The combobox that the dialog belongs to.",
+ "description": "The combobox popup.",
+ "jsdocTags": []
+ },
+ {
+ "name": "filterMode",
+ "type": "InputSignal<\"manual\" | \"auto-select\" | \"highlight\">",
+ "memberType": "property",
+ "memberTags": [],
+ "description": "The filter mode for the combobox.\n- `manual`: The consumer is responsible for filtering the options.\n- `auto-select`: The combobox automatically selects the first matching option.\n- `highlight`: The combobox highlights matching text in the options without changing selection.",
+ "jsdocTags": []
+ },
+ {
+ "name": "disabled",
+ "type": "InputSignalWithTransform",
+ "memberType": "property",
+ "memberTags": [
+ "readonly"
+ ],
+ "description": "Whether the combobox is disabled.",
+ "jsdocTags": []
+ },
+ {
+ "name": "readonly",
+ "type": "InputSignalWithTransform",
+ "memberType": "property",
+ "memberTags": [
+ "readonly"
+ ],
+ "description": "Whether the combobox is read-only.",
"jsdocTags": []
},
+ {
+ "name": "firstMatch",
+ "type": "InputSignal",
+ "memberType": "property",
+ "memberTags": [
+ "readonly"
+ ],
+ "description": "The value of the first matching item in the popup.",
+ "jsdocTags": []
+ },
+ {
+ "name": "expanded",
+ "type": "Signal",
+ "memberType": "property",
+ "memberTags": [
+ "readonly"
+ ],
+ "description": "Whether the combobox is expanded.",
+ "jsdocTags": []
+ },
+ {
+ "name": "alwaysExpanded",
+ "type": "InputSignalWithTransform",
+ "memberType": "property",
+ "memberTags": [
+ "readonly"
+ ],
+ "description": "Whether the combobox popup should always be expanded, regardless of user interaction.",
+ "jsdocTags": []
+ },
+ {
+ "name": "inputElement",
+ "type": "Signal",
+ "memberType": "property",
+ "memberTags": [
+ "readonly"
+ ],
+ "description": "Input element connected to the combobox, if any.",
+ "jsdocTags": []
+ },
+ {
+ "name": "open",
+ "signatures": [
+ {
+ "name": "open",
+ "entryType": "function",
+ "description": "Opens the combobox to the selected item.",
+ "generics": [],
+ "isNewType": false,
+ "jsdocTags": [],
+ "params": [],
+ "rawComment": "/** Opens the combobox to the selected item. */",
+ "returnType": "void"
+ }
+ ],
+ "implementation": {
+ "params": [],
+ "isNewType": false,
+ "returnType": "void",
+ "generics": [],
+ "name": "open",
+ "description": "Opens the combobox to the selected item.",
+ "entryType": "function",
+ "jsdocTags": [],
+ "rawComment": "/** Opens the combobox to the selected item. */"
+ },
+ "entryType": "function",
+ "description": "Opens the combobox to the selected item.",
+ "jsdocTags": [],
+ "rawComment": "/** Opens the combobox to the selected item. */",
+ "memberType": "method",
+ "memberTags": []
+ },
{
"name": "close",
"signatures": [
{
"name": "close",
"entryType": "function",
- "description": "",
+ "description": "Closes the combobox.",
"generics": [],
"isNewType": false,
"jsdocTags": [],
"params": [],
- "rawComment": "",
+ "rawComment": "/** Closes the combobox. */",
"returnType": "void"
}
],
@@ -367,60 +437,63 @@
"returnType": "void",
"generics": [],
"name": "close",
- "description": "",
+ "description": "Closes the combobox.",
"entryType": "function",
"jsdocTags": [],
- "rawComment": ""
+ "rawComment": "/** Closes the combobox. */"
},
"entryType": "function",
- "description": "",
+ "description": "Closes the combobox.",
"jsdocTags": [],
- "rawComment": "",
+ "rawComment": "/** Closes the combobox. */",
"memberType": "method",
"memberTags": []
}
],
- "generics": [],
- "description": "Integrates a native `` element with the combobox, allowing for\na modal or non-modal popup experience. It handles the opening and closing of the dialog\nbased on the combobox's expanded state.\n\n```html\n\n \n \n \n \n```",
+ "generics": [
+ {
+ "name": "V"
+ }
+ ],
+ "description": "The container element that wraps a combobox input and popup, and orchestrates its behavior.\n\nThe `ngCombobox` directive is the main entry point for creating a combobox and customizing its\nbehavior. It coordinates the interactions between the `ngComboboxInput` and the popup, which\nis defined by a `ng-template` with the `ngComboboxPopupContainer` directive. If using the\n`CdkOverlay`, the `cdkConnectedOverlay` directive takes the place of `ngComboboxPopupContainer`.\n\n```html\n\n
\n\n
\n \n @for (option of filteredOptions(); track option) {\n
\n {{option}} \n
\n }\n
\n \n
\n```",
"jsdocTags": [
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Combobox](guide/aria/combobox)"
+ },
+ {
+ "name": "see",
+ "comment": "[Select](guide/aria/select)"
+ },
+ {
+ "name": "see",
+ "comment": "[Multiselect](guide/aria/multiselect)"
+ },
+ {
+ "name": "see",
+ "comment": "[Autocomplete](guide/aria/autocomplete)"
}
],
- "rawComment": "/**\n * Integrates a native `` element with the combobox, allowing for\n * a modal or non-modal popup experience. It handles the opening and closing of the dialog\n * based on the combobox's expanded state.\n *\n * ```html\n * \n * \n * \n * \n * \n * ```\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * The container element that wraps a combobox input and popup, and orchestrates its behavior.\n *\n * The `ngCombobox` directive is the main entry point for creating a combobox and customizing its\n * behavior. It coordinates the interactions between the `ngComboboxInput` and the popup, which\n * is defined by a `ng-template` with the `ngComboboxPopupContainer` directive. If using the\n * `CdkOverlay`, the `cdkConnectedOverlay` directive takes the place of `ngComboboxPopupContainer`.\n *\n * ```html\n * \n *
\n *\n *
\n * \n * @for (option of filteredOptions(); track option) {\n *
\n * {{option}} \n *
\n * }\n *
\n * \n *
\n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */",
"implements": [],
- "isStandalone": true,
- "selector": "dialog[ngComboboxDialog]",
- "exportAs": [
- "ngComboboxDialog"
- ],
"source": {
- "filePath": "src/aria/combobox/combobox.ts",
- "startLine": 317,
- "endLine": 367
+ "filePath": "/src/aria/combobox/combobox.ts",
+ "startLine": 60,
+ "endLine": 162
}
}
],
"symbols": [
[
- "afterRenderEffect",
- "@angular/core"
- ],
- [
- "booleanAttribute",
- "@angular/core"
- ],
- [
- "computed",
- "@angular/core"
- ],
- [
- "contentChild",
+ "Directive",
"@angular/core"
],
[
- "Directive",
+ "afterRenderEffect",
"@angular/core"
],
[
@@ -431,18 +504,10 @@
"inject",
"@angular/core"
],
- [
- "input",
- "@angular/core"
- ],
[
"model",
"@angular/core"
],
- [
- "signal",
- "@angular/core"
- ],
[
"untracked",
"@angular/core"
@@ -452,47 +517,39 @@
"@angular/core"
],
[
- "DeferredContent",
- "@angular/aria/private"
- ],
- [
- "DeferredContentAware",
- "@angular/aria/private"
+ "signal",
+ "@angular/core"
],
[
- "ComboboxPattern",
- "@angular/aria/private"
+ "booleanAttribute",
+ "@angular/core"
],
[
- "ComboboxListboxControls",
- "@angular/aria/private"
+ "computed",
+ "@angular/core"
],
[
- "ComboboxTreeControls",
- "@angular/aria/private"
+ "contentChild",
+ "@angular/core"
],
[
- "ComboboxDialogPattern",
- "@angular/aria/private"
+ "input",
+ "@angular/core"
],
[
"Directionality",
"@angular/cdk/bidi"
],
[
- "toSignal",
- "@angular/core/rxjs-interop"
- ],
- [
- "Combobox",
+ "ComboboxPopupContainer",
"@angular/aria/combobox"
],
[
- "ComboboxInput",
+ "ComboboxDialog",
"@angular/aria/combobox"
],
[
- "ComboboxPopupContainer",
+ "ComboboxInput",
"@angular/aria/combobox"
],
[
@@ -500,103 +557,103 @@
"@angular/aria/combobox"
],
[
- "ComboboxDialog",
+ "Combobox",
"@angular/aria/combobox"
],
[
- "Combobox",
+ "ComboboxPopupContainer",
"@angular/aria/combobox"
],
[
- "Combobox.textDirection",
+ "ComboboxDialog",
"@angular/aria/combobox"
],
[
- "Combobox.element",
+ "ComboboxDialog.element",
"@angular/aria/combobox"
],
[
- "Combobox.popup",
+ "ComboboxDialog.combobox",
"@angular/aria/combobox"
],
[
- "Combobox.filterMode",
+ "ComboboxDialog.close",
"@angular/aria/combobox"
],
[
- "Combobox.disabled",
+ "ComboboxInput",
"@angular/aria/combobox"
],
[
- "Combobox.readonly",
+ "ComboboxInput.element",
"@angular/aria/combobox"
],
[
- "Combobox.firstMatch",
+ "ComboboxInput.combobox",
"@angular/aria/combobox"
],
[
- "Combobox.expanded",
+ "ComboboxInput.value",
"@angular/aria/combobox"
],
[
- "Combobox.alwaysExpanded",
+ "ComboboxPopup",
"@angular/aria/combobox"
],
[
- "Combobox.inputElement",
+ "ComboboxPopup.combobox",
"@angular/aria/combobox"
],
[
- "Combobox.open",
+ "Combobox",
"@angular/aria/combobox"
],
[
- "Combobox.close",
+ "Combobox.textDirection",
"@angular/aria/combobox"
],
[
- "ComboboxInput",
+ "Combobox.element",
"@angular/aria/combobox"
],
[
- "ComboboxInput.element",
+ "Combobox.popup",
"@angular/aria/combobox"
],
[
- "ComboboxInput.combobox",
+ "Combobox.filterMode",
"@angular/aria/combobox"
],
[
- "ComboboxInput.value",
+ "Combobox.disabled",
"@angular/aria/combobox"
],
[
- "ComboboxPopupContainer",
+ "Combobox.readonly",
"@angular/aria/combobox"
],
[
- "ComboboxPopup",
+ "Combobox.firstMatch",
"@angular/aria/combobox"
],
[
- "ComboboxPopup.combobox",
+ "Combobox.expanded",
"@angular/aria/combobox"
],
[
- "ComboboxDialog",
+ "Combobox.alwaysExpanded",
"@angular/aria/combobox"
],
[
- "ComboboxDialog.element",
+ "Combobox.inputElement",
"@angular/aria/combobox"
],
[
- "ComboboxDialog.combobox",
+ "Combobox.open",
"@angular/aria/combobox"
],
[
- "ComboboxDialog.close",
+ "Combobox.close",
"@angular/aria/combobox"
]
]
diff --git a/adev/src/content/aria/aria-grid.json b/adev/src/content/aria/aria-grid.json
index 11d4dffcfdf5..ecd363f90612 100755
--- a/adev/src/content/aria/aria-grid.json
+++ b/adev/src/content/aria/aria-grid.json
@@ -5,7 +5,7 @@
"normalizedModuleName": "angular_aria_grid",
"entries": [
{
- "name": "Grid",
+ "name": "GridRow",
"isAbstract": false,
"entryType": "directive",
"members": [
@@ -20,214 +20,266 @@
"jsdocTags": []
},
{
- "name": "textDirection",
- "type": "any",
+ "name": "rowIndex",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
- "readonly"
+ "readonly",
+ "input"
],
- "description": "Text direction.",
- "jsdocTags": []
+ "description": "The index of this row within the grid.",
+ "jsdocTags": [],
+ "inputAlias": "rowIndex",
+ "isRequiredInput": false
+ }
+ ],
+ "generics": [],
+ "description": "Represents a row within a grid. It is a container for `ngGridCell` directives.\n\n```html\n\n \n \n```",
+ "jsdocTags": [
+ {
+ "name": "developerPreview",
+ "comment": "21.0"
},
{
- "name": "enableSelection",
- "type": "any",
+ "name": "see",
+ "comment": "[Grid](guide/aria/grid)"
+ }
+ ],
+ "rawComment": "/**\n * Represents a row within a grid. It is a container for `ngGridCell` directives.\n *\n * ```html\n * \n * \n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Grid](guide/aria/grid)\n */",
+ "implements": [],
+ "isStandalone": true,
+ "selector": "[ngGridRow]",
+ "exportAs": [
+ "ngGridRow"
+ ],
+ "aliases": [
+ "ngGridRow"
+ ],
+ "source": {
+ "filePath": "/src/aria/grid/grid-row.ts",
+ "startLine": 35,
+ "endLine": 74
+ }
+ },
+ {
+ "name": "GridCellWidget",
+ "isAbstract": false,
+ "entryType": "directive",
+ "members": [
+ {
+ "name": "element",
+ "type": "HTMLElement",
"memberType": "property",
"memberTags": [
- "readonly",
- "input"
+ "readonly"
],
- "description": "Whether selection is enabled for the grid.",
- "jsdocTags": [],
- "inputAlias": "enableSelection",
- "isRequiredInput": false
+ "description": "A reference to the host element.",
+ "jsdocTags": []
},
{
- "name": "disabled",
- "type": "any",
+ "name": "active",
+ "type": "Signal",
"memberType": "property",
"memberTags": [
- "readonly",
- "input"
+ "readonly"
],
- "description": "Whether the grid is disabled.",
- "jsdocTags": [],
- "inputAlias": "disabled",
- "isRequiredInput": false
+ "description": "Whether the widget is currently active (focused).",
+ "jsdocTags": []
},
{
- "name": "softDisabled",
- "type": "any",
+ "name": "id",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "Whether to allow disabled items to receive focus. When `true`, disabled items are\nfocusable but not interactive. When `false`, disabled items are skipped during navigation.",
+ "description": "A unique identifier for the widget.",
"jsdocTags": [],
- "inputAlias": "softDisabled",
+ "inputAlias": "id",
"isRequiredInput": false
},
{
- "name": "focusMode",
- "type": "any",
+ "name": "widgetType",
+ "type": "InputSignal<\"simple\" | \"complex\" | \"editable\">",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The focus strategy used by the grid.\n- `roving`: Focus is moved to the active cell using `tabindex`.\n- `activedescendant`: Focus remains on the grid container, and `aria-activedescendant` is used to indicate the active cell.",
+ "description": "The type of widget, which determines how it is activated.",
"jsdocTags": [],
- "inputAlias": "focusMode",
+ "inputAlias": "widgetType",
"isRequiredInput": false
},
{
- "name": "rowWrap",
- "type": "any",
+ "name": "disabled",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The wrapping behavior for keyboard navigation along the row axis.\n- `continuous`: Navigation wraps from the last row to the first, and vice-versa.\n- `loop`: Navigation wraps within the current row.\n- `nowrap`: Navigation stops at the first/last item in the row.",
+ "description": "Whether the widget is disabled.",
"jsdocTags": [],
- "inputAlias": "rowWrap",
+ "inputAlias": "disabled",
"isRequiredInput": false
},
{
- "name": "colWrap",
- "type": "any",
+ "name": "focusTarget",
+ "type": "InputSignal | undefined>",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The wrapping behavior for keyboard navigation along the column axis.\n- `continuous`: Navigation wraps from the last column to the first, and vice-versa.\n- `loop`: Navigation wraps within the current column.\n- `nowrap`: Navigation stops at the first/last item in the column.",
+ "description": "The target that will receive focus instead of the widget.",
"jsdocTags": [],
- "inputAlias": "colWrap",
+ "inputAlias": "focusTarget",
"isRequiredInput": false
},
{
- "name": "multi",
- "type": "any",
+ "name": "activated",
+ "type": "OutputEmitterRef",
"memberType": "property",
"memberTags": [
"readonly",
- "input"
+ "output"
],
- "description": "Whether multiple cells in the grid can be selected.",
+ "description": "Emits when the widget is activated.",
"jsdocTags": [],
- "inputAlias": "multi",
- "isRequiredInput": false
+ "outputAlias": "activated"
},
{
- "name": "selectionMode",
- "type": "any",
+ "name": "deactivated",
+ "type": "OutputEmitterRef",
"memberType": "property",
"memberTags": [
"readonly",
- "input"
+ "output"
],
- "description": "The selection strategy used by the grid.\n- `follow`: The focused cell is automatically selected.\n- `explicit`: Cells are selected explicitly by the user (e.g., via click or spacebar).",
+ "description": "Emits when the widget is deactivated.",
"jsdocTags": [],
- "inputAlias": "selectionMode",
- "isRequiredInput": false
+ "outputAlias": "deactivated"
},
{
- "name": "enableRangeSelection",
- "type": "any",
+ "name": "tabindex",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "Whether enable range selections (with modifier keys or dragging).",
+ "description": "The tabindex override.",
"jsdocTags": [],
- "inputAlias": "enableRangeSelection",
+ "inputAlias": "tabindex",
"isRequiredInput": false
- }
- ],
- "generics": [],
- "description": "The container for a grid. It provides keyboard navigation and focus management for the grid's\nrows and cells. It manages the overall behavior of the grid, including focus\nwrapping, selection, and disabled states.\n\n```html\n",
- "jsdocTags": [
- {
- "name": "for",
- "comment": "(row of gridData; track row) {\n"
},
{
- "name": "for",
- "comment": "(cell of row; track cell) {\n \n {{cell.value}}\n \n}\n \n}\n
\n```"
+ "name": "isActivated",
+ "type": "Signal",
+ "memberType": "getter",
+ "memberTags": [],
+ "description": "Whether the widget is activated.",
+ "jsdocTags": []
},
{
- "name": "developerPreview",
- "comment": "21.0"
- }
- ],
- "rawComment": "/**\n * The container for a grid. It provides keyboard navigation and focus management for the grid's\n * rows and cells. It manages the overall behavior of the grid, including focus\n * wrapping, selection, and disabled states.\n *\n * ```html\n * \n * @for (row of gridData; track row) {\n * \n * @for (cell of row; track cell) {\n * \n * {{cell.value}}\n * \n * }\n * \n * }\n *
\n * ```\n *\n * @developerPreview 21.0\n */",
- "implements": [],
- "isStandalone": true,
- "selector": "[ngGrid]",
- "exportAs": [
- "ngGrid"
- ],
- "source": {
- "filePath": "src/aria/grid/grid.ts",
- "startLine": 47,
- "endLine": 164
- }
- },
- {
- "name": "GridRow",
- "isAbstract": false,
- "entryType": "directive",
- "members": [
- {
- "name": "element",
- "type": "HTMLElement",
- "memberType": "property",
- "memberTags": [
- "readonly"
+ "name": "activate",
+ "signatures": [
+ {
+ "name": "activate",
+ "entryType": "function",
+ "description": "Activates the widget.",
+ "generics": [],
+ "isNewType": false,
+ "jsdocTags": [],
+ "params": [],
+ "rawComment": "/** Activates the widget. */",
+ "returnType": "void"
+ }
],
- "description": "A reference to the host element.",
- "jsdocTags": []
+ "implementation": {
+ "params": [],
+ "isNewType": false,
+ "returnType": "void",
+ "generics": [],
+ "name": "activate",
+ "description": "Activates the widget.",
+ "entryType": "function",
+ "jsdocTags": [],
+ "rawComment": "/** Activates the widget. */"
+ },
+ "entryType": "function",
+ "description": "Activates the widget.",
+ "jsdocTags": [],
+ "rawComment": "/** Activates the widget. */",
+ "memberType": "method",
+ "memberTags": []
},
{
- "name": "rowIndex",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly",
- "input"
+ "name": "deactivate",
+ "signatures": [
+ {
+ "name": "deactivate",
+ "entryType": "function",
+ "description": "Deactivates the widget.",
+ "generics": [],
+ "isNewType": false,
+ "jsdocTags": [],
+ "params": [],
+ "rawComment": "/** Deactivates the widget. */",
+ "returnType": "void"
+ }
],
- "description": "The index of this row within the grid.",
+ "implementation": {
+ "params": [],
+ "isNewType": false,
+ "returnType": "void",
+ "generics": [],
+ "name": "deactivate",
+ "description": "Deactivates the widget.",
+ "entryType": "function",
+ "jsdocTags": [],
+ "rawComment": "/** Deactivates the widget. */"
+ },
+ "entryType": "function",
+ "description": "Deactivates the widget.",
"jsdocTags": [],
- "inputAlias": "rowIndex",
- "isRequiredInput": false
+ "rawComment": "/** Deactivates the widget. */",
+ "memberType": "method",
+ "memberTags": []
}
],
"generics": [],
- "description": "Represents a row within a grid. It is a container for `ngGridCell` directives.\n\n```html\n\n \n \n```",
+ "description": "Represents an interactive element inside a `GridCell`. It allows for pausing grid navigation to\ninteract with the widget.\n\nWhen the user interacts with the widget (e.g., by typing in an input or opening a menu), grid\nnavigation is temporarily suspended to allow the widget to handle keyboard\nevents.\n\n```html\n\n Click Me \n \n```",
"jsdocTags": [
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Grid](guide/aria/grid)"
}
],
- "rawComment": "/**\n * Represents a row within a grid. It is a container for `ngGridCell` directives.\n *\n * ```html\n * \n * \n * \n * ```\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * Represents an interactive element inside a `GridCell`. It allows for pausing grid navigation to\n * interact with the widget.\n *\n * When the user interacts with the widget (e.g., by typing in an input or opening a menu), grid\n * navigation is temporarily suspended to allow the widget to handle keyboard\n * events.\n *\n * ```html\n * \n * Click Me \n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Grid](guide/aria/grid)\n */",
"implements": [],
"isStandalone": true,
- "selector": "[ngGridRow]",
+ "selector": "[ngGridCellWidget]",
"exportAs": [
- "ngGridRow"
+ "ngGridCellWidget"
+ ],
+ "aliases": [
+ "ngGridCellWidget"
],
"source": {
- "filePath": "src/aria/grid/grid.ts",
- "startLine": 177,
- "endLine": 216
+ "filePath": "src/aria/grid/grid-cell-widget.ts",
+ "startLine": 42,
+ "endLine": 134
}
},
{
- "name": "GridCell",
+ "name": "Grid",
"isAbstract": false,
"entryType": "directive",
"members": [
@@ -241,19 +293,9 @@
"description": "A reference to the host element.",
"jsdocTags": []
},
- {
- "name": "active",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly"
- ],
- "description": "Whether the cell is currently active (focused).",
- "jsdocTags": []
- },
{
"name": "textDirection",
- "type": "any",
+ "type": "WritableSignal",
"memberType": "property",
"memberTags": [
"readonly"
@@ -262,187 +304,153 @@
"jsdocTags": []
},
{
- "name": "id",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly",
- "input"
- ],
- "description": "A unique identifier for the cell.",
- "jsdocTags": [],
- "inputAlias": "id",
- "isRequiredInput": false
- },
- {
- "name": "role",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly",
- "input"
- ],
- "description": "The ARIA role for the cell.",
- "jsdocTags": [],
- "inputAlias": "role",
- "isRequiredInput": false
- },
- {
- "name": "rowSpan",
- "type": "any",
+ "name": "enableSelection",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The number of rows the cell should span.",
+ "description": "Whether selection is enabled for the grid.",
"jsdocTags": [],
- "inputAlias": "rowSpan",
+ "inputAlias": "enableSelection",
"isRequiredInput": false
},
{
- "name": "colSpan",
- "type": "any",
+ "name": "disabled",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The number of columns the cell should span.",
+ "description": "Whether the grid is disabled.",
"jsdocTags": [],
- "inputAlias": "colSpan",
+ "inputAlias": "disabled",
"isRequiredInput": false
},
{
- "name": "rowIndex",
- "type": "any",
+ "name": "softDisabled",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The index of this cell's row within the grid.",
+ "description": "Whether to allow disabled items to receive focus. When `true`, disabled items are\nfocusable but not interactive. When `false`, disabled items are skipped during navigation.",
"jsdocTags": [],
- "inputAlias": "rowIndex",
+ "inputAlias": "softDisabled",
"isRequiredInput": false
},
{
- "name": "colIndex",
- "type": "any",
+ "name": "focusMode",
+ "type": "InputSignal<\"roving\" | \"activedescendant\">",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The index of this cell's column within the grid.",
+ "description": "The focus strategy used by the grid.\n- `roving`: Focus is moved to the active cell using `tabindex`.\n- `activedescendant`: Focus remains on the grid container, and `aria-activedescendant` is used to indicate the active cell.",
"jsdocTags": [],
- "inputAlias": "colIndex",
+ "inputAlias": "focusMode",
"isRequiredInput": false
},
{
- "name": "disabled",
- "type": "any",
+ "name": "rowWrap",
+ "type": "InputSignal<\"continuous\" | \"loop\" | \"nowrap\">",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "Whether the cell is disabled.",
+ "description": "The wrapping behavior for keyboard navigation along the row axis.\n- `continuous`: Navigation wraps from the last row to the first, and vice-versa.\n- `loop`: Navigation wraps within the current row.\n- `nowrap`: Navigation stops at the first/last item in the row.",
"jsdocTags": [],
- "inputAlias": "disabled",
+ "inputAlias": "rowWrap",
"isRequiredInput": false
},
{
- "name": "selected",
- "type": "any",
- "memberType": "property",
- "memberTags": [
- "readonly",
- "input",
- "output"
- ],
- "description": "Whether the cell is selected.",
- "jsdocTags": [],
- "inputAlias": "selected",
- "isRequiredInput": false,
- "outputAlias": "selectedChange"
- },
- {
- "name": "selectable",
- "type": "any",
+ "name": "colWrap",
+ "type": "InputSignal<\"continuous\" | \"loop\" | \"nowrap\">",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "Whether the cell is selectable.",
+ "description": "The wrapping behavior for keyboard navigation along the column axis.\n- `continuous`: Navigation wraps from the last column to the first, and vice-versa.\n- `loop`: Navigation wraps within the current column.\n- `nowrap`: Navigation stops at the first/last item in the column.",
"jsdocTags": [],
- "inputAlias": "selectable",
+ "inputAlias": "colWrap",
"isRequiredInput": false
},
{
- "name": "orientation",
- "type": "any",
+ "name": "multi",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "Orientation of the widgets in the cell.",
+ "description": "Whether multiple cells in the grid can be selected.",
"jsdocTags": [],
- "inputAlias": "orientation",
+ "inputAlias": "multi",
"isRequiredInput": false
},
{
- "name": "wrap",
- "type": "any",
+ "name": "selectionMode",
+ "type": "InputSignal<\"follow\" | \"explicit\">",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "Whether widgets navigation wraps.",
+ "description": "The selection strategy used by the grid.\n- `follow`: The focused cell is automatically selected.\n- `explicit`: Cells are selected explicitly by the user (e.g., via click or spacebar).",
"jsdocTags": [],
- "inputAlias": "wrap",
+ "inputAlias": "selectionMode",
"isRequiredInput": false
},
{
- "name": "tabindex",
- "type": "any",
+ "name": "enableRangeSelection",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The tabindex override.",
+ "description": "Whether enable range selections (with modifier keys or dragging).",
"jsdocTags": [],
- "inputAlias": "tabindex",
+ "inputAlias": "enableRangeSelection",
"isRequiredInput": false
}
],
"generics": [],
- "description": "Represents a cell within a grid row. It is the primary focusable element\nwithin the grid. It can be disabled and can have its selection state managed\nthrough the `selected` input.\n\n```html\n\n Cell Content\n \n```",
+ "description": "The container for a grid. It provides keyboard navigation and focus management for the grid's\nrows and cells. It manages the overall behavior of the grid, including focus\nwrapping, selection, and disabled states.\n\n```html\n\n @for (row of gridData; track row) {\n \n @for (cell of row; track cell) {\n \n {{cell.value}}\n \n }\n \n }\n
\n```",
"jsdocTags": [
{
"name": "developerPreview",
"comment": "21.0"
+ },
+ {
+ "name": "see",
+ "comment": "[Grid](guide/aria/grid)"
}
],
- "rawComment": "/**\n * Represents a cell within a grid row. It is the primary focusable element\n * within the grid. It can be disabled and can have its selection state managed\n * through the `selected` input.\n *\n * ```html\n * \n * Cell Content\n * \n * ```\n *\n * @developerPreview 21.0\n */",
+ "rawComment": "/**\n * The container for a grid. It provides keyboard navigation and focus management for the grid's\n * rows and cells. It manages the overall behavior of the grid, including focus\n * wrapping, selection, and disabled states.\n *\n * ```html\n * \n * @for (row of gridData; track row) {\n * \n * @for (cell of row; track cell) {\n * \n * {{cell.value}}\n * \n * }\n * \n * }\n *
\n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Grid](guide/aria/grid)\n */",
"implements": [],
"isStandalone": true,
- "selector": "[ngGridCell]",
+ "selector": "[ngGrid]",
"exportAs": [
- "ngGridCell"
+ "ngGrid"
+ ],
+ "aliases": [
+ "ngGrid"
],
"source": {
- "filePath": "src/aria/grid/grid.ts",
- "startLine": 231,
- "endLine": 346
+ "filePath": "/src/aria/grid/grid.ts",
+ "startLine": 48,
+ "endLine": 177
}
},
{
- "name": "GridCellWidget",
+ "name": "GridCell",
"isAbstract": false,
"entryType": "directive",
"members": [
@@ -458,93 +466,172 @@
},
{
"name": "active",
- "type": "any",
+ "type": "Signal",
"memberType": "property",
"memberTags": [
"readonly"
],
- "description": "Whether the widget is currently active (focused).",
+ "description": "Whether the cell is currently active (focused).",
+ "jsdocTags": []
+ },
+ {
+ "name": "textDirection",
+ "type": "WritableSignal",
+ "memberType": "property",
+ "memberTags": [
+ "readonly"
+ ],
+ "description": "Text direction.",
"jsdocTags": []
},
{
"name": "id",
- "type": "any",
+ "type": "InputSignal",
+ "memberType": "property",
+ "memberTags": [
+ "readonly",
+ "input"
+ ],
+ "description": "A unique identifier for the cell.",
+ "jsdocTags": [],
+ "inputAlias": "id",
+ "isRequiredInput": false
+ },
+ {
+ "name": "role",
+ "type": "InputSignal<\"gridcell\" | \"columnheader\" | \"rowheader\">",
+ "memberType": "property",
+ "memberTags": [
+ "readonly",
+ "input"
+ ],
+ "description": "The ARIA role for the cell.",
+ "jsdocTags": [],
+ "inputAlias": "role",
+ "isRequiredInput": false
+ },
+ {
+ "name": "rowSpan",
+ "type": "InputSignal",
+ "memberType": "property",
+ "memberTags": [
+ "readonly",
+ "input"
+ ],
+ "description": "The number of rows the cell should span.",
+ "jsdocTags": [],
+ "inputAlias": "rowSpan",
+ "isRequiredInput": false
+ },
+ {
+ "name": "colSpan",
+ "type": "InputSignal",
+ "memberType": "property",
+ "memberTags": [
+ "readonly",
+ "input"
+ ],
+ "description": "The number of columns the cell should span.",
+ "jsdocTags": [],
+ "inputAlias": "colSpan",
+ "isRequiredInput": false
+ },
+ {
+ "name": "rowIndex",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "A unique identifier for the widget.",
+ "description": "The index of this cell's row within the grid.",
"jsdocTags": [],
- "inputAlias": "id",
+ "inputAlias": "rowIndex",
"isRequiredInput": false
},
{
- "name": "widgetType",
- "type": "any",
+ "name": "colIndex",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The type of widget, which determines how it is activated.",
+ "description": "The index of this cell's column within the grid.",
"jsdocTags": [],
- "inputAlias": "widgetType",
+ "inputAlias": "colIndex",
"isRequiredInput": false
},
{
"name": "disabled",
- "type": "any",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "Whether the widget is disabled.",
+ "description": "Whether the cell is disabled.",
"jsdocTags": [],
"inputAlias": "disabled",
"isRequiredInput": false
},
{
- "name": "focusTarget",
- "type": "any",
+ "name": "selected",
+ "type": "ModelSignal",
+ "memberType": "property",
+ "memberTags": [
+ "readonly",
+ "input",
+ "output"
+ ],
+ "description": "Whether the cell is selected.",
+ "jsdocTags": [],
+ "inputAlias": "selected",
+ "isRequiredInput": false,
+ "outputAlias": "selectedChange"
+ },
+ {
+ "name": "selectable",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly",
"input"
],
- "description": "The target that will receive focus instead of the widget.",
+ "description": "Whether the cell is selectable.",
"jsdocTags": [],
- "inputAlias": "focusTarget",
+ "inputAlias": "selectable",
"isRequiredInput": false
},
{
- "name": "onActivate",
- "type": "any",
+ "name": "orientation",
+ "type": "InputSignal<\"vertical\" | \"horizontal\">",
"memberType": "property",
"memberTags": [
"readonly",
- "output"
+ "input"
],
- "description": "Emits when the widget is activated.",
+ "description": "Orientation of the widgets in the cell.",
"jsdocTags": [],
- "outputAlias": "onActivate"
+ "inputAlias": "orientation",
+ "isRequiredInput": false
},
{
- "name": "onDeactivate",
- "type": "any",
+ "name": "wrap",
+ "type": "InputSignalWithTransform",
"memberType": "property",
"memberTags": [
"readonly",
- "output"
+ "input"
],
- "description": "Emits when the widget is deactivated.",
+ "description": "Whether widgets navigation wraps.",
"jsdocTags": [],
- "outputAlias": "onDeactivate"
+ "inputAlias": "wrap",
+ "isRequiredInput": false
},
{
"name": "tabindex",
- "type": "any",
+ "type": "InputSignal",
"memberType": "property",
"memberTags": [
"readonly",
@@ -554,151 +641,84 @@
"jsdocTags": [],
"inputAlias": "tabindex",
"isRequiredInput": false
- },
- {
- "name": "isActivated",
- "type": "Signal",
- "memberType": "getter",
- "memberTags": [],
- "description": "Whether the widget is activated.",
- "jsdocTags": []
- },
- {
- "name": "activate",
- "signatures": [
- {
- "name": "activate",
- "entryType": "function",
- "description": "Activates the widget.",
- "generics": [],
- "isNewType": false,
- "jsdocTags": [],
- "params": [],
- "rawComment": "/** Activates the widget. */",
- "returnType": "void"
- }
- ],
- "implementation": {
- "params": [],
- "isNewType": false,
- "returnType": "void",
- "generics": [],
- "name": "activate",
- "description": "Activates the widget.",
- "entryType": "function",
- "jsdocTags": [],
- "rawComment": "/** Activates the widget. */"
- },
- "entryType": "function",
- "description": "Activates the widget.",
- "jsdocTags": [],
- "rawComment": "/** Activates the widget. */",
- "memberType": "method",
- "memberTags": []
- },
- {
- "name": "deactivate",
- "signatures": [
- {
- "name": "deactivate",
- "entryType": "function",
- "description": "Deactivates the widget.",
- "generics": [],
- "isNewType": false,
- "jsdocTags": [],
- "params": [],
- "rawComment": "/** Deactivates the widget. */",
- "returnType": "void"
- }
- ],
- "implementation": {
- "params": [],
- "isNewType": false,
- "returnType": "void",
- "generics": [],
- "name": "deactivate",
- "description": "Deactivates the widget.",
- "entryType": "function",
- "jsdocTags": [],
- "rawComment": "/** Deactivates the widget. */"
- },
- "entryType": "function",
- "description": "Deactivates the widget.",
- "jsdocTags": [],
- "rawComment": "/** Deactivates the widget. */",
- "memberType": "method",
- "memberTags": []
}
],
"generics": [],
- "description": "Represents an interactive element inside a `GridCell`. It allows for pausing grid navigation to\ninteract with the widget.\n\nWhen the user interacts with the widget (e.g., by typing in an input or opening a menu), grid\nnavigation is temporarily suspended to allow the widget to handle keyboard\nevents.\n\n```html\n