From 876a06105404eb16190ec33a3a71d5843cbd301a Mon Sep 17 00:00:00 2001 From: felipdsa21 Date: Sun, 18 Oct 2026 15:50:47 -0300 Subject: [PATCH 1/3] docs(multiple): remove "first" from aria content directive descriptions By default, the content of `ngTabContent` and `ngAccordionContent` is created again each time the panel is shown, not only the first time. --- src/aria/accordion/accordion-content.ts | 2 +- src/aria/tabs/tab-content.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aria/accordion/accordion-content.ts b/src/aria/accordion/accordion-content.ts index af0e6a5eea08..12d09b8f6b06 100644 --- a/src/aria/accordion/accordion-content.ts +++ b/src/aria/accordion/accordion-content.ts @@ -15,7 +15,7 @@ import {DeferredContent} from '../private'; * * This directive should be applied to an `ng-template` inside an `ngAccordionPanel`. * It allows the content of the panel to be lazily rendered, improving performance - * by only creating the content when the panel is first expanded. + * by only creating the content when the panel is expanded. * * ```html *
diff --git a/src/aria/tabs/tab-content.ts b/src/aria/tabs/tab-content.ts index 9188094c9606..fcaec044ea84 100644 --- a/src/aria/tabs/tab-content.ts +++ b/src/aria/tabs/tab-content.ts @@ -14,7 +14,7 @@ import {DeferredContent} from '../private'; * * This structural directive should be applied to an `ng-template` within an `ngTabPanel`. * It enables lazy loading of the tab's content, meaning the content is only rendered - * when the tab is activated for the first time. + * when the tab is activated. * * ```html *
From f08ef66e98ccad743630edf2b6f55541a46b5b70 Mon Sep 17 00:00:00 2001 From: felipdsa21 Date: Fri, 16 Oct 2026 12:26:15 -0300 Subject: [PATCH 2/3] fix(multiple): allow aria tab and accordion panels without content directive Content placed directly in `ngTabPanel` or `ngAccordionPanel` is rendered eagerly, but a dev-mode warning claims the panel needs `ngTabContent` or `ngAccordionContent` to render. Removes the warning so eager content is supported alongside lazy content. Fixes #33950 --- src/aria/accordion/accordion-panel.ts | 24 +++++--------------- src/aria/accordion/accordion.spec.ts | 26 ++++++++++------------ src/aria/tabs/tab-panel.ts | 7 ------ src/aria/tabs/tabs.spec.ts | 32 +++++++++++++++------------ 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/src/aria/accordion/accordion-panel.ts b/src/aria/accordion/accordion-panel.ts index 89d30917de64..0fe04c6b0559 100644 --- a/src/aria/accordion/accordion-panel.ts +++ b/src/aria/accordion/accordion-panel.ts @@ -6,28 +6,19 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - Directive, - ElementRef, - afterRenderEffect, - computed, - contentChild, - inject, - input, -} from '@angular/core'; +import {Directive, ElementRef, afterRenderEffect, computed, inject, input} from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {DeferredContentAware, AccordionTriggerPattern, reportViolations} from '../private'; -import {AccordionContent} from './accordion-content'; /** * The content panel of an accordion item that is conditionally visible. * * This directive is a container for the content that is shown or hidden. It should * expose a template reference that will be used by the corresponding `ngAccordionTrigger`. - * The content within the panel should be provided using an `ng-template` with the - * `ngAccordionContent` directive so that the content is not rendered on the page until the trigger - * is expanded. It applies `role="region"` for accessibility and uses the `inert` attribute to hide - * its content from assistive technologies when not visible. + * To render the content lazily, provide it using an `ng-template` with the `ngAccordionContent` + * directive so that it is not rendered on the page until the trigger is expanded. It applies + * `role="region"` for accessibility and uses the `inert` attribute to hide its content from + * assistive technologies when not visible. * * ```html *
@@ -65,8 +56,6 @@ export class AccordionPanel { /** The DeferredContentAware host directive. */ private readonly _deferredContentAware = inject(DeferredContentAware); - private readonly _accordionContent = contentChild(AccordionContent); - /** A global unique identifier for the panel. */ readonly id = input(inject(_IdGenerator).getId('ng-accordion-panel-', true)); @@ -94,9 +83,6 @@ export class AccordionPanel { read: () => { const violations: string[] = []; - if (!this._accordionContent()) { - violations.push('ngAccordionPanel must have an ngAccordionContent to render.'); - } if (!this._pattern) { violations.push('ngAccordionPanel must have an ngAccordionTrigger to control it.'); } diff --git a/src/aria/accordion/accordion.spec.ts b/src/aria/accordion/accordion.spec.ts index 12d0515b8ad9..b9403418650c 100644 --- a/src/aria/accordion/accordion.spec.ts +++ b/src/aria/accordion/accordion.spec.ts @@ -487,6 +487,17 @@ describe('AccordionGroup', () => { }); }); + describe('eager content', () => { + beforeEach(async () => { + fixture = TestBed.createComponent(AccordionPanelWithEagerContent) as any; + await fixture.whenStable(); + }); + + it('should render ngAccordionPanel content without ngAccordionContent', () => { + expect(fixture.nativeElement.textContent).toContain('Content'); + }); + }); + describe('structural validations', () => { let consoleSpy: jasmine.Spy; @@ -530,19 +541,6 @@ describe('AccordionGroup', () => { ); }); - it('should warn when ngAccordionPanel is missing ngAccordionContent', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [AccordionPanelWithoutContent], - }); - const noContentFixture = TestBed.createComponent(AccordionPanelWithoutContent); - noContentFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'ngAccordionPanel must have an ngAccordionContent to render.', - ); - }); - it('should warn when ngAccordionPanel is missing controlling trigger', () => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ @@ -737,7 +735,7 @@ class AccordionWithNestedTrigger {} imports: [AccordionGroup, AccordionTrigger, AccordionPanel], changeDetection: ChangeDetectionStrategy.Eager, }) -class AccordionPanelWithoutContent {} +class AccordionPanelWithEagerContent {} @Component({ template: ` diff --git a/src/aria/tabs/tab-panel.ts b/src/aria/tabs/tab-panel.ts index 42eb5759240b..da1be15fd710 100644 --- a/src/aria/tabs/tab-panel.ts +++ b/src/aria/tabs/tab-panel.ts @@ -14,13 +14,11 @@ import { inject, input, afterRenderEffect, - contentChild, OnInit, OnDestroy, } from '@angular/core'; import {TabPanelPattern, DeferredContentAware, reportViolations} from '../private'; import {TABS} from './tab-tokens'; -import {TabContent} from './tab-content'; /** * A TabPanel container for the resources of layered content associated with a tab. @@ -89,8 +87,6 @@ export class TabPanel implements OnInit, OnDestroy { tab: this._tabPattern, }); - private readonly _tabContent = contentChild(TabContent); - constructor() { // Connect the panel's hidden state to the DeferredContentAware's visibility. afterRenderEffect({ @@ -105,9 +101,6 @@ export class TabPanel implements OnInit, OnDestroy { read: () => { const violations: string[] = []; - if (!this._tabContent()) { - violations.push('ngTabPanel must have an ngTabContent structural directive to render.'); - } if (!this._tabs._tabMap().has(this.value())) { violations.push( `ngTabPanel with value '${this.value()}' does not have a corresponding ngTab.`, diff --git a/src/aria/tabs/tabs.spec.ts b/src/aria/tabs/tabs.spec.ts index 065677e040d1..1a6d88bfcac6 100644 --- a/src/aria/tabs/tabs.spec.ts +++ b/src/aria/tabs/tabs.spec.ts @@ -807,6 +807,23 @@ describe('Tabs', () => { }); }); + describe('Eager content', () => { + let eagerFixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [provideFakeDirectionality('ltr')], + }); + eagerFixture = TestBed.createComponent(PanelWithEagerContentComponent); + fixture = eagerFixture as any; + eagerFixture.detectChanges(); + }); + + it('should render ngTabPanel content without ngTabContent', () => { + expect(eagerFixture.nativeElement.textContent).toContain('Content 1'); + }); + }); + describe('structural validations', () => { let consoleSpy: jasmine.Spy; @@ -845,19 +862,6 @@ describe('Tabs', () => { ); }); - it('should warn when ngTabPanel is missing ngTabContent structural directive', () => { - TestBed.resetTestingModule(); - TestBed.configureTestingModule({ - imports: [PanelWithoutContentComponent], - }); - const noContentFixture = TestBed.createComponent(PanelWithoutContentComponent); - noContentFixture.detectChanges(); - - expect(consoleSpy).toHaveBeenCalledWith( - 'ngTabPanel must have an ngTabContent structural directive to render.', - ); - }); - it('should warn when duplicate values are detected inside ngTabList', () => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ @@ -986,7 +990,7 @@ class PanelWithoutTabComponent {} imports: [Tabs, TabList, Tab, TabPanel], changeDetection: ChangeDetectionStrategy.Eager, }) -class PanelWithoutContentComponent {} +class PanelWithEagerContentComponent {} @Component({ template: ` From 9535ecfb60da84927a7417ab726af45e60b0b4d9 Mon Sep 17 00:00:00 2001 From: felipdsa21 Date: Sun, 18 Oct 2026 15:53:37 -0300 Subject: [PATCH 3/3] build: update api goldens --- goldens/aria/accordion/index.api.md | 2 +- goldens/aria/tabs/index.api.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/goldens/aria/accordion/index.api.md b/goldens/aria/accordion/index.api.md index 5e74cb835bb6..1ffd96cecb55 100644 --- a/goldens/aria/accordion/index.api.md +++ b/goldens/aria/accordion/index.api.md @@ -54,7 +54,7 @@ export class AccordionPanel { toggle(): void; readonly visible: _angular_core.Signal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; } diff --git a/goldens/aria/tabs/index.api.md b/goldens/aria/tabs/index.api.md index 7426ed6e6694..f346e64d79c3 100644 --- a/goldens/aria/tabs/index.api.md +++ b/goldens/aria/tabs/index.api.md @@ -86,7 +86,7 @@ export class TabPanel implements OnInit, OnDestroy { readonly value: _angular_core.InputSignal; readonly visible: _angular_core.Signal; // (undocumented) - static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: _angular_core.ɵɵFactoryDeclaration; }