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
Lines changed: 5 additions & 2 deletions
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
});
Lines changed: 8 additions & 13 deletions
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

Lines changed: 5 additions & 4 deletions
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>
Lines changed: 26 additions & 18 deletions
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

Lines changed: 1 addition & 1 deletion
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 {
Lines changed: 12 additions & 18 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy