Skip to content

Commit cfac5c7

Browse files
committed
refactor(aria/accordion): Replace ContentChildren with manual registration of triggers
1 parent a6d60cf commit cfac5c7

File tree

5 files changed

+52
-9
lines changed

5 files changed

+52
-9
lines changed

goldens/aria/accordion/index.api.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ export class AccordionGroup {
2525
expandAll(): void;
2626
readonly multiExpandable: _angular_core.InputSignalWithTransform<boolean, unknown>;
2727
readonly _pattern: AccordionGroupPattern;
28+
_registerTrigger(trigger: AccordionTrigger): void;
2829
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
2930
readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>;
31+
_unregisterTrigger(trigger: AccordionTrigger): void;
3032
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
3133
// (undocumented)
32-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AccordionGroup, "[ngAccordionGroup]", ["ngAccordionGroup"], { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "multiExpandable": { "alias": "multiExpandable"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; }, {}, ["_triggers"], never, true, never>;
34+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AccordionGroup, "[ngAccordionGroup]", ["ngAccordionGroup"], { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "multiExpandable": { "alias": "multiExpandable"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3335
// (undocumented)
3436
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AccordionGroup, never>;
3537
}
@@ -51,22 +53,25 @@ export class AccordionPanel {
5153
}
5254

5355
// @public
54-
export class AccordionTrigger implements OnInit {
56+
export class AccordionTrigger implements OnInit, OnDestroy {
5557
readonly active: _angular_core.Signal<boolean>;
5658
collapse(): void;
5759
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
5860
readonly element: HTMLElement;
5961
expand(): void;
6062
readonly expanded: _angular_core.ModelSignal<boolean>;
6163
readonly id: _angular_core.InputSignal<string>;
64+
readonly index: _angular_core.InputSignal<number | undefined>;
65+
// (undocumented)
66+
ngOnDestroy(): void;
6267
// (undocumented)
6368
ngOnInit(): void;
6469
readonly panel: _angular_core.InputSignal<AccordionPanel>;
6570
readonly panelId: _angular_core.Signal<string>;
6671
_pattern: AccordionTriggerPattern;
6772
toggle(): void;
6873
// (undocumented)
69-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AccordionTrigger, "[ngAccordionTrigger]", ["ngAccordionTrigger"], { "panel": { "alias": "panel"; "required": true; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; }, never, never, true, never>;
74+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<AccordionTrigger, "[ngAccordionTrigger]", ["ngAccordionTrigger"], { "panel": { "alias": "panel"; "required": true; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "index": { "alias": "index"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; }, never, never, true, never>;
7075
// (undocumented)
7176
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AccordionTrigger, never>;
7277
}

src/aria/accordion/accordion-group.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ import {
1111
ElementRef,
1212
booleanAttribute,
1313
computed,
14-
contentChildren,
1514
inject,
1615
input,
1716
signal,
1817
} from '@angular/core';
1918
import {Directionality} from '@angular/cdk/bidi';
20-
import {AccordionGroupPattern} from '../private';
19+
import {AccordionGroupPattern, sortDirectives} from '../private';
2120
import {AccordionTrigger} from './accordion-trigger';
2221
import {ACCORDION_GROUP} from './accordion-tokens';
2322

@@ -75,10 +74,23 @@ export class AccordionGroup {
7574
readonly element = this._elementRef.nativeElement as HTMLElement;
7675

7776
/** The AccordionTriggers nested inside this group. */
78-
private readonly _triggers = contentChildren(AccordionTrigger, {descendants: true});
77+
private readonly _triggers = signal(new Set<AccordionTrigger>());
78+
79+
/** The AccordionTriggers nested inside this group. */
80+
private readonly _sortedTriggers = computed(() => {
81+
const triggers = [...this._triggers()] as AccordionTrigger[];
82+
const sortFn =
83+
triggers[0]?.index() === undefined
84+
? sortDirectives
85+
: (a: AccordionTrigger, b: AccordionTrigger) => a.index()! - b.index()!;
86+
87+
return triggers.sort(sortFn);
88+
});
7989

8090
/** The corresponding patterns for the accordion triggers. */
81-
private readonly _triggerPatterns = computed(() => this._triggers().map(t => t._pattern));
91+
private readonly _triggerPatterns = computed(() => {
92+
return this._sortedTriggers().map(t => t._pattern);
93+
});
8294

8395
/** The text direction (ltr or rtl). */
8496
readonly textDirection = inject(Directionality).valueSignal;
@@ -117,4 +129,16 @@ export class AccordionGroup {
117129
collapseAll() {
118130
this._pattern.collapseAll();
119131
}
132+
133+
/** Internal method to register each trigger as we can not use contentChildren. */
134+
_registerTrigger(trigger: AccordionTrigger) {
135+
this._triggers().add(trigger);
136+
this._triggers.set(new Set(this._triggers()));
137+
}
138+
139+
/** Internal method to unregister each trigger as we can not use contentChildren. */
140+
_unregisterTrigger(trigger: AccordionTrigger) {
141+
this._triggers().delete(trigger);
142+
this._triggers.set(new Set(this._triggers()));
143+
}
120144
}

src/aria/accordion/accordion-trigger.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import {
1010
Directive,
1111
ElementRef,
12+
OnDestroy,
1213
OnInit,
1314
booleanAttribute,
1415
computed,
@@ -53,7 +54,7 @@ import {AccordionPanel} from './accordion-panel';
5354
'[attr.tabindex]': '_pattern.tabIndex()',
5455
},
5556
})
56-
export class AccordionTrigger implements OnInit {
57+
export class AccordionTrigger implements OnInit, OnDestroy {
5758
/** A reference to the trigger element. */
5859
private readonly _elementRef = inject(ElementRef);
5960

@@ -69,12 +70,15 @@ export class AccordionTrigger implements OnInit {
6970
/** The unique identifier for the trigger. */
7071
readonly id = input(inject(_IdGenerator).getId('ng-accordion-trigger-', true));
7172

72-
/** The unique identifier for the correspondingtrigger panel. */
73+
/** The unique identifier for the corresponding trigger panel. */
7374
readonly panelId = computed(() => this.panel().id());
7475

7576
/** Whether the trigger is disabled. */
7677
readonly disabled = input(false, {transform: booleanAttribute});
7778

79+
/** The index of the trigger within the accordion group. */
80+
readonly index = input<number>();
81+
7882
/** Whether the corresponding panel is expanded. */
7983
readonly expanded = model<boolean>(false);
8084

@@ -93,6 +97,14 @@ export class AccordionTrigger implements OnInit {
9397
});
9498

9599
this.panel()._pattern = this._pattern;
100+
101+
this._accordionGroup._registerTrigger(this);
102+
}
103+
104+
ngOnDestroy() {
105+
this.panel()._pattern = undefined;
106+
107+
this._accordionGroup._unregisterTrigger(this);
96108
}
97109

98110
/** Expands this item. */

src/aria/accordion/accordion.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ describe('AccordionGroup', () => {
461461
<div class="item-container">
462462
<button
463463
ngAccordionTrigger
464+
[index]="$index"
464465
[panel]="panel"
465466
[disabled]="item.disabled()"
466467
[(expanded)]="item.expanded"

src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ <h3 class="example-accordion-header">
1919
<button ngAccordionTrigger
2020
#trigger="ngAccordionTrigger"
2121
class="example-accordion-trigger"
22+
[index]="$index"
2223
[disabled]="item.disabled()"
2324
[(expanded)]="item.expanded"
2425
[panel]="panel">

0 commit comments

Comments
 (0)