diff --git a/goldens/aria/accordion/index.api.md b/goldens/aria/accordion/index.api.md index 98049f6ad2d4..21f5c56ea0a6 100644 --- a/goldens/aria/accordion/index.api.md +++ b/goldens/aria/accordion/index.api.md @@ -8,7 +8,6 @@ import * as _angular_cdk_bidi from '@angular/cdk/bidi'; import * as _angular_core from '@angular/core'; import { InjectionToken } from '@angular/core'; import { OnDestroy } from '@angular/core'; -import { OnInit } from '@angular/core'; import { Signal } from '@angular/core'; // @public @@ -60,7 +59,7 @@ export class AccordionPanel { } // @public -export class AccordionTrigger implements OnInit, OnDestroy { +export class AccordionTrigger { constructor(); readonly active: _angular_core.Signal; collapse(): void; @@ -69,10 +68,6 @@ export class AccordionTrigger implements OnInit, OnDestroy { expand(): void; readonly expanded: _angular_core.ModelSignal; readonly id: _angular_core.InputSignal; - // (undocumented) - ngOnDestroy(): void; - // (undocumented) - ngOnInit(): void; readonly panel: _angular_core.InputSignal; readonly panelId: _angular_core.Signal; readonly _pattern: AccordionTriggerPattern; diff --git a/src/aria/accordion/accordion-trigger.ts b/src/aria/accordion/accordion-trigger.ts index 041f28224f9f..d53faab44f9a 100644 --- a/src/aria/accordion/accordion-trigger.ts +++ b/src/aria/accordion/accordion-trigger.ts @@ -9,8 +9,8 @@ import { Directive, ElementRef, - OnDestroy, - OnInit, + effect, + untracked, booleanAttribute, computed, inject, @@ -60,7 +60,7 @@ import {AccordionPanel} from './accordion-panel'; 'ngAccordionTrigger': '', }, }) -export class AccordionTrigger implements OnInit, OnDestroy { +export class AccordionTrigger { /** A reference to the trigger element. */ private readonly _elementRef = inject(ElementRef); @@ -97,6 +97,18 @@ export class AccordionTrigger implements OnInit, OnDestroy { readonly active = computed(() => this._pattern.active()); constructor() { + effect(onCleanup => { + const panel = this.panel(); + untracked(() => { + panel._pattern = this._pattern; + this._accordionGroup._collection.register(this); + }); + onCleanup(() => { + panel._pattern = undefined; + this._accordionGroup._collection.unregister(this); + }); + }); + // Automatically prevent form submission. if (this.element.tagName === 'BUTTON' && !this.element.hasAttribute('type')) { this.element.setAttribute('type', 'button'); @@ -125,18 +137,6 @@ export class AccordionTrigger implements OnInit, OnDestroy { } } - ngOnInit() { - this.panel()._pattern = this._pattern; - - this._accordionGroup._collection.register(this); - } - - ngOnDestroy() { - this.panel()._pattern = undefined; - - this._accordionGroup._collection.unregister(this); - } - /** Expands this item. */ expand() { this._pattern.open(); diff --git a/src/aria/accordion/accordion.spec.ts b/src/aria/accordion/accordion.spec.ts index c4ee7ca9f0ec..da40f7e1c066 100644 --- a/src/aria/accordion/accordion.spec.ts +++ b/src/aria/accordion/accordion.spec.ts @@ -825,3 +825,96 @@ class AccordionPanelWithoutTrigger {} changeDetection: ChangeDetectionStrategy.Eager, }) class AccordionWithMultipleExpandedItems {} + +describe('Accordion destruction', () => { + it('should update the panel association when the panel input changes', async () => { + const fixture = TestBed.createComponent(AccordionWithChangingPanel); + await fixture.whenStable(); + const panels = fixture.debugElement + .queryAll(By.directive(AccordionPanel)) + .map(el => el.injector.get(AccordionPanel)); + const trigger = fixture.debugElement + .query(By.directive(AccordionTrigger)) + .injector.get(AccordionTrigger); + const group = fixture.debugElement + .query(By.directive(AccordionGroup)) + .injector.get(AccordionGroup); + + expect(panels[0]._pattern).toBe(trigger._pattern); + expect(panels[1]._pattern).toBeUndefined(); + expect(group._collection.orderedItems()).toEqual([trigger]); + + fixture.componentInstance.panel.set(panels[1]); + await fixture.whenStable(); + + expect(panels[0]._pattern).toBeUndefined(); + expect(panels[1]._pattern).toBe(trigger._pattern); + expect(group._collection.orderedItems()).toEqual([trigger]); + + trigger.expand(); + await fixture.whenStable(); + expect(trigger.expanded()).toBeTrue(); + panels[1].collapse(); + await fixture.whenStable(); + expect(trigger.expanded()).toBeFalse(); + + fixture.destroy(); + expect(panels[1]._pattern).toBeUndefined(); + expect(group._collection.orderedItems()).toEqual([]); + }); + + it('should destroy a trigger before the first change detection', () => { + const fixture = TestBed.createComponent(StaticAccordion); + + expect(() => fixture.destroy()).not.toThrow(); + }); + + it('should clear the panel association when destroying an initialized trigger', async () => { + const fixture = TestBed.createComponent(StaticAccordion); + await fixture.whenStable(); + + const panel = fixture.debugElement + .query(By.directive(AccordionPanel)) + .injector.get(AccordionPanel); + const trigger = fixture.debugElement + .query(By.directive(AccordionTrigger)) + .injector.get(AccordionTrigger); + expect(panel._pattern).toBe(trigger._pattern); + + expect(() => fixture.destroy()).not.toThrow(); + expect(panel._pattern).toBeUndefined(); + }); +}); + +@Component({ + template: ` +
+ +
+ Content +
+
+ `, + imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], + changeDetection: ChangeDetectionStrategy.Eager, +}) +class StaticAccordion {} + +@Component({ + template: ` +
+ +
+ First +
+
+ Second +
+
+ `, + imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], + changeDetection: ChangeDetectionStrategy.Eager, +}) +class AccordionWithChangingPanel { + panel = signal(undefined); +}