Skip to content

Commit 3578bbd

Browse files
committed
refactor(accordion): input signals, host bindings
1 parent af82588 commit 3578bbd

File tree

6 files changed

+57
-56
lines changed

6 files changed

+57
-56
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { AccordionButtonDirective } from './accordion-button.directive';
2+
import { TestBed } from '@angular/core/testing';
23

34
describe('AccordionButtonDirective', () => {
45
it('should create an instance', () => {
5-
const directive = new AccordionButtonDirective();
6-
expect(directive).toBeTruthy();
6+
TestBed.runInInjectionContext(() => {
7+
const directive = new AccordionButtonDirective();
8+
expect(directive).toBeTruthy();
9+
});
710
});
811
});
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,30 @@
1-
import { Directive, HostBinding, Input } from '@angular/core';
1+
import { computed, Directive, input } from '@angular/core';
22

33
@Directive({
44
selector: '[cAccordionButton]',
55
standalone: true,
6-
host: { class: 'accordion-button' }
6+
host: { '[class]': 'hostClasses()', '[attr.type]': 'type()', '[attr.aria-expanded]': 'ariaExpanded()' }
77
})
88
export class AccordionButtonDirective {
99
/**
1010
* Toggles an accordion button collapsed state. Use in accordionHeaderTemplate. [docs]
1111
* @type boolean
1212
*/
13-
@Input() collapsed!: boolean;
13+
readonly collapsed = input<boolean | undefined>(undefined);
1414

1515
/**
1616
* Default type for cAccordionButton. [docs]
1717
* @type string
1818
* @default 'button'
1919
*/
20-
@HostBinding('attr.type')
21-
@Input()
22-
type: string = 'button';
20+
readonly type = input('button');
2321

24-
@HostBinding('class')
25-
get hostClasses(): any {
22+
readonly hostClasses = computed(() => {
2623
return {
2724
'accordion-button': true,
28-
collapsed: this.collapsed
25+
collapsed: this.collapsed()
2926
};
30-
}
27+
});
3128

32-
@HostBinding('attr.aria-expanded') get ariaExpanded(): boolean {
33-
return !this.collapsed;
34-
}
29+
readonly ariaExpanded = computed(() => !this.collapsed());
3530
}

projects/coreui-angular/src/lib/accordion/accordion-item/accordion-item.component.html

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1+
@let tmpl = templates();
12
<ng-container>
23
<div class="accordion-header">
3-
<ng-container *ngTemplateOutlet="templates['accordionHeaderTemplate'] || defaultAccordionHeaderTemplate; context: itemContext" />
4+
<ng-container *ngTemplateOutlet="tmpl['accordionHeaderTemplate'] || defaultAccordionHeaderTemplate; context: itemContext" />
45
</div>
56
<div class="accordion-collapse" cCollapse [visible]="visible" [attr.aria-expanded]="visible" [id]="contentId">
6-
<ng-container *ngTemplateOutlet="templates['accordionBodyTemplate'] || defaultAccordionBodyTemplate; context: itemContext" />
7+
<ng-container *ngTemplateOutlet="tmpl['accordionBodyTemplate'] || defaultAccordionBodyTemplate; context: itemContext" />
78
</div>
89
</ng-container>
910

1011
<ng-template #defaultAccordionHeaderTemplate>
1112
<button cAccordionButton [collapsed]="!visible" [attr.aria-controls]="contentId" (click)="toggleItem()">
1213
<ng-container
13-
*ngTemplateOutlet="templates['accordionHeader'] || defaultAccordionHeaderContentTemplate; context: itemContext">
14+
*ngTemplateOutlet="tmpl['accordionHeader'] || defaultAccordionHeaderContentTemplate; context: itemContext">
1415
</ng-container>
1516
</button>
1617
</ng-template>
@@ -22,7 +23,7 @@
2223
<ng-template #defaultAccordionBodyTemplate>
2324
<div class="accordion-body">
2425
<ng-container
25-
*ngTemplateOutlet="templates['accordionBody'] || defaultAccordionBodyContentTemplate; context: itemContext">
26+
*ngTemplateOutlet="tmpl['accordionBody'] || defaultAccordionBodyContentTemplate; context: itemContext">
2627
</ng-container>
2728
</div>
2829
</ng-template>
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {
2-
AfterContentInit,
32
booleanAttribute,
43
Component,
5-
ContentChildren,
4+
computed,
5+
contentChildren,
6+
inject,
67
Input,
78
OnDestroy,
89
OnInit,
9-
QueryList
10+
TemplateRef
1011
} from '@angular/core';
1112
import { NgTemplateOutlet } from '@angular/common';
1213

@@ -26,15 +27,15 @@ let nextId = 0;
2627
imports: [AccordionButtonDirective, NgTemplateOutlet, CollapseDirective],
2728
host: { class: 'accordion-item' }
2829
})
29-
export class AccordionItemComponent implements OnInit, OnDestroy, AfterContentInit {
30-
constructor(private accordionService: AccordionService) {}
30+
export class AccordionItemComponent implements OnInit, OnDestroy {
31+
readonly #accordionService = inject(AccordionService);
3132

3233
/**
3334
* Toggle an accordion item programmatically
3435
* @type boolean
3536
* @default false
3637
*/
37-
@Input({ transform: booleanAttribute }) visible: string | boolean = false;
38+
@Input({ transform: booleanAttribute }) visible: boolean = false;
3839

3940
@Input()
4041
set open(value: boolean) {
@@ -47,25 +48,32 @@ export class AccordionItemComponent implements OnInit, OnDestroy, AfterContentIn
4748
}
4849

4950
contentId = `accordion-item-${nextId++}`;
50-
itemContext = { $implicit: <boolean>this.visible };
51-
templates: any = {};
52-
@ContentChildren(TemplateIdDirective, { descendants: true }) contentTemplates!: QueryList<TemplateIdDirective>;
51+
52+
get itemContext() {
53+
return { $implicit: <boolean>this.visible };
54+
}
55+
56+
readonly contentTemplates = contentChildren(TemplateIdDirective, { descendants: true });
57+
58+
readonly templates = computed(() => {
59+
return this.contentTemplates().reduce(
60+
(acc, child) => {
61+
acc[child.id] = child.templateRef;
62+
return acc;
63+
},
64+
{} as Record<string, TemplateRef<any>>
65+
);
66+
});
5367

5468
ngOnInit(): void {
55-
this.accordionService.addItem(this);
69+
this.#accordionService.addItem(this);
5670
}
5771

5872
ngOnDestroy(): void {
59-
this.accordionService.removeItem(this);
73+
this.#accordionService.removeItem(this);
6074
}
6175

6276
toggleItem(): void {
63-
this.accordionService.toggleItem(this);
64-
}
65-
66-
ngAfterContentInit(): void {
67-
this.contentTemplates.forEach((child: TemplateIdDirective) => {
68-
this.templates[child.id] = child.templateRef;
69-
});
77+
this.#accordionService.toggleItem(this);
7078
}
7179
}

projects/coreui-angular/src/lib/accordion/accordion.service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable } from '@angular/core';
2-
import { AccordionItemComponent } from './accordion-item/accordion-item.component';
2+
import type { AccordionItemComponent } from './accordion-item/accordion-item.component';
33

44
@Injectable()
55
export class AccordionService {
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { booleanAttribute, Component, HostBinding, inject, Input } from '@angular/core';
1+
import { booleanAttribute, Component, computed, effect, inject, input } from '@angular/core';
22

33
import { AccordionService } from '../accordion.service';
44

@@ -9,35 +9,29 @@ import { AccordionService } from '../accordion.service';
99
exportAs: 'cAccordionItem',
1010
providers: [AccordionService],
1111
standalone: true,
12-
host: { class: 'accordion' }
12+
host: { '[class]': 'hostClasses()' }
1313
})
1414
export class AccordionComponent {
15-
#accordionService = inject(AccordionService);
15+
readonly #accordionService = inject(AccordionService);
1616

1717
/**
1818
* Removes the default background-color, some borders, and some rounded corners to render accordions edge-to-edge with their parent container.
1919
* @type boolean
2020
*/
21-
@Input({ transform: booleanAttribute }) flush: boolean = false;
21+
readonly flush = input(false, { transform: booleanAttribute });
2222

2323
/**
2424
* Make accordion items stay open when another item is opened
2525
* @type boolean
2626
*/
27-
@Input({ transform: booleanAttribute })
28-
set alwaysOpen(value: boolean) {
29-
this.#accordionService.alwaysOpen = value;
30-
}
27+
readonly alwaysOpen = input(false, { transform: booleanAttribute });
3128

32-
get alwaysOpen(): boolean {
33-
return this.#accordionService.alwaysOpen;
34-
}
29+
readonly #alwaysOpenEffect = effect(() => {
30+
this.#accordionService.alwaysOpen = this.alwaysOpen();
31+
});
3532

36-
@HostBinding('class')
37-
get hostClasses(): any {
38-
return {
39-
accordion: true,
40-
'accordion-flush': this.flush
41-
};
42-
}
33+
readonly hostClasses = computed<Record<string, boolean>>(() => ({
34+
accordion: true,
35+
'accordion-flush': this.flush()
36+
}));
4337
}

0 commit comments

Comments
 (0)