Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f61b4f3

Browse filesBrowse files
committed
refactor(carousel): signal inputs, host bindings, cleanup, tests
1 parent c141c5f commit f61b4f3
Copy full SHA for f61b4f3
Expand file treeCollapse file tree

15 files changed

+311
-257
lines changed
+6-5Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Component, HostBinding } from '@angular/core';
1+
import { Component } from '@angular/core';
22

33
@Component({
44
selector: 'c-carousel-caption',
55
template: '<ng-content />',
6-
styleUrls: ['./carousel-caption.component.scss']
6+
styleUrls: ['./carousel-caption.component.scss'],
7+
host: {
8+
'[class.carousel-caption]': 'true'
9+
}
710
})
8-
export class CarouselCaptionComponent {
9-
@HostBinding('class.carousel-caption') carouselCaptionClass = true;
10-
}
11+
export class CarouselCaptionComponent {}
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
@if (hasContent) {
1+
@if (hasContent()) {
22
<div #content>
33
<ng-content />
44
</div>
55
} @else {
6-
<span [class]="carouselControlIconClass" [attr.aria-label]="direction" [attr.aria-hidden]="true"></span>
7-
<span class="visually-hidden">{{ caption }}</span>
6+
<span [class]="carouselControlIconClass()" [attr.aria-label]="direction()" [attr.aria-hidden]="true"></span>
7+
<span class="visually-hidden">{{ caption() }}</span>
88
}
+53-3Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
1-
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
1+
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
22

33
import { CarouselControlComponent } from './carousel-control.component';
44
import { CarouselService } from '../carousel.service';
55
import { CarouselState } from '../carousel-state';
6+
import { ComponentRef, DebugElement } from '@angular/core';
7+
import { take } from 'rxjs/operators';
68

79
describe('CarouselControlComponent', () => {
810
let component: CarouselControlComponent;
11+
let componentRef: ComponentRef<CarouselControlComponent>;
912
let fixture: ComponentFixture<CarouselControlComponent>;
1013
let service: CarouselService;
1114
let state: CarouselState;
15+
let debugElement: DebugElement;
1216

1317
beforeEach(waitForAsync(() => {
1418
TestBed.configureTestingModule({
1519
imports: [CarouselControlComponent],
1620
providers: [CarouselService, CarouselState]
17-
})
18-
.compileComponents();
21+
}).compileComponents();
1922
}));
2023

2124
beforeEach(() => {
2225
fixture = TestBed.createComponent(CarouselControlComponent);
2326
component = fixture.componentInstance;
27+
componentRef = fixture.componentRef;
2428
service = TestBed.inject(CarouselService);
2529
state = TestBed.inject(CarouselState);
30+
debugElement = fixture.debugElement;
2631
fixture.detectChanges();
2732
});
2833

@@ -33,4 +38,49 @@ describe('CarouselControlComponent', () => {
3338
it('should have css class="carousel-control-next"', () => {
3439
expect(fixture.nativeElement).toHaveClass('carousel-control-next');
3540
});
41+
42+
it('should have role="button"', () => {
43+
expect(fixture.nativeElement.getAttribute('role')).toBe('button');
44+
});
45+
46+
it('should have caption="Next"', () => {
47+
expect(component.caption()).toBe('Next');
48+
});
49+
50+
it('should have caption to be undefined', () => {
51+
componentRef.setInput('caption', 'Test');
52+
expect(component.caption()).toBe('Test');
53+
});
54+
55+
it('should have direction="next"', () => {
56+
expect(component.direction()).toBe('next');
57+
});
58+
59+
it('should have carouselControlIconClass="carousel-control-next-icon"', () => {
60+
expect(component.carouselControlIconClass()).toBe('carousel-control-next-icon');
61+
});
62+
63+
it('should play on click', fakeAsync(() => {
64+
componentRef.setInput('direction', 'prev');
65+
component.onClick(new MouseEvent('click'));
66+
fixture.detectChanges();
67+
expect(component.caption()).toBe('Previous');
68+
}));
69+
70+
it('should play on keyup', fakeAsync(() => {
71+
service.carouselIndex$.pipe(take(2)).subscribe((index) => {
72+
if (index.active === 0) {
73+
expect(index).toEqual({ active: 0, interval: -1, lastItemIndex: -1 });
74+
} else {
75+
expect(index).toEqual({});
76+
}
77+
});
78+
79+
debugElement.nativeElement.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowLeft' }));
80+
tick();
81+
debugElement.nativeElement.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowRight' }));
82+
tick();
83+
debugElement.nativeElement.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter' }));
84+
tick();
85+
}));
3686
});

‎projects/coreui-angular/src/lib/carousel/carousel-control/carousel-control.component.ts

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/carousel/carousel-control/carousel-control.component.ts
+42-52Lines changed: 42 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,77 @@
1-
import {
2-
AfterViewInit,
3-
ChangeDetectorRef,
4-
Component,
5-
ElementRef,
6-
HostBinding,
7-
HostListener,
8-
inject,
9-
Input,
10-
ViewChild
11-
} from '@angular/core';
1+
import { Component, computed, ElementRef, inject, input, linkedSignal, viewChild } from '@angular/core';
122

133
import { CarouselState } from '../carousel-state';
144

155
@Component({
166
selector: 'c-carousel-control',
17-
templateUrl: './carousel-control.component.html'
7+
templateUrl: './carousel-control.component.html',
8+
exportAs: 'cCarouselControl',
9+
host: {
10+
'[attr.role]': 'role()',
11+
'[class]': 'hostClasses()',
12+
'(keyup)': 'onKeyUp($event)',
13+
'(click)': 'onClick($event)'
14+
}
1815
})
19-
export class CarouselControlComponent implements AfterViewInit {
20-
readonly #changeDetectorRef = inject(ChangeDetectorRef);
16+
export class CarouselControlComponent {
2117
readonly #carouselState = inject(CarouselState);
2218

2319
/**
2420
* Carousel control caption. [docs]
25-
* @type string
21+
* @return string
2622
*/
27-
@Input()
28-
set caption(value) {
29-
this.#caption = value;
30-
}
23+
readonly captionInput = input<string | undefined>(undefined, { alias: 'caption' });
3124

32-
get caption(): string {
33-
return !!this.#caption ? this.#caption : this.direction === 'prev' ? 'Previous' : 'Next';
34-
}
35-
#caption?: string;
25+
readonly caption = linkedSignal({
26+
source: () => this.captionInput(),
27+
computation: (value) => {
28+
return !!value ? value : this.direction() === 'prev' ? 'Previous' : 'Next';
29+
}
30+
});
3631

3732
/**
38-
* Carousel control direction. [docs]
39-
* @type {'next' | 'prev'}
33+
* Carousel control direction.
34+
* @return {'next' | 'prev'}
4035
*/
41-
@Input() direction: 'prev' | 'next' = 'next';
36+
readonly direction = input<'prev' | 'next'>('next');
4237

43-
@HostBinding('attr.role')
44-
get hostRole(): string {
45-
return 'button';
46-
}
38+
/**
39+
* Carousel control role.
40+
* @return string
41+
*/
42+
readonly role = input('button');
4743

48-
@HostBinding('class')
49-
get hostClasses(): string {
50-
return `carousel-control-${this.direction}`;
51-
}
44+
readonly hostClasses = computed(() => {
45+
return `carousel-control-${this.direction()}`;
46+
});
5247

53-
get carouselControlIconClass(): string {
54-
return `carousel-control-${this.direction}-icon`;
55-
}
48+
readonly carouselControlIconClass = computed(() => {
49+
return `carousel-control-${this.direction()}-icon`;
50+
});
5651

57-
@ViewChild('content') content?: ElementRef;
52+
readonly content = viewChild('content', { read: ElementRef });
5853

59-
hasContent = true;
54+
readonly hasContent = computed(() => {
55+
return this.content()?.nativeElement.childNodes.length ?? false;
56+
});
6057

61-
@HostListener('keyup', ['$event'])
6258
onKeyUp($event: KeyboardEvent): void {
6359
if ($event.key === 'Enter') {
64-
this.play();
60+
this.#play();
6561
}
6662
if ($event.key === 'ArrowLeft') {
67-
this.play('prev');
63+
this.#play('prev');
6864
}
6965
if ($event.key === 'ArrowRight') {
70-
this.play('next');
66+
this.#play('next');
7167
}
7268
}
7369

74-
@HostListener('click', ['$event'])
75-
public onClick($event: MouseEvent): void {
76-
this.play();
77-
}
78-
79-
ngAfterViewInit(): void {
80-
this.hasContent = this.content?.nativeElement.childNodes.length ?? false;
81-
this.#changeDetectorRef.detectChanges();
70+
onClick($event: MouseEvent): void {
71+
this.#play();
8272
}
8373

84-
private play(direction = this.direction): void {
74+
#play(direction = this.direction()): void {
8575
const nextIndex = this.#carouselState.direction(direction);
8676
this.#carouselState.state = { activeItemIndex: nextIndex };
8777
}

‎projects/coreui-angular/src/lib/carousel/carousel-indicators/carousel-indicators.component.spec.ts

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/carousel/carousel-indicators/carousel-indicators.component.spec.ts
+13-2Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,30 @@ describe('CarouselIndicatorsComponent', () => {
1414
TestBed.configureTestingModule({
1515
imports: [CarouselIndicatorsComponent],
1616
providers: [CarouselService, CarouselState]
17-
})
18-
.compileComponents();
17+
}).compileComponents();
1918
}));
2019

2120
beforeEach(() => {
2221
fixture = TestBed.createComponent(CarouselIndicatorsComponent);
2322
service = TestBed.inject(CarouselService);
2423
state = TestBed.inject(CarouselState);
24+
state.setItems([]);
2525
component = fixture.componentInstance;
26+
component.items = [0, 1, 2, 3];
2627
fixture.detectChanges();
2728
});
2829

2930
it('should create', () => {
3031
expect(component).toBeTruthy();
3132
});
33+
34+
it('should set active index', () => {
35+
service.setIndex({ active: 1 });
36+
expect(component.active).toBe(1);
37+
});
38+
39+
it('should call onClick', () => {
40+
component.onClick(2);
41+
expect(component.active).toBe(2);
42+
});
3243
});
+10-22Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
1-
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
2-
import { Subscription } from 'rxjs';
1+
import { Component, DestroyRef, inject, OnInit } from '@angular/core';
32

43
import { CarouselState } from '../carousel-state';
54
import { CarouselService } from '../carousel.service';
5+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
66

77
@Component({
88
selector: 'c-carousel-indicators',
99
templateUrl: './carousel-indicators.component.html'
1010
})
11-
export class CarouselIndicatorsComponent implements OnInit, OnDestroy {
11+
export class CarouselIndicatorsComponent implements OnInit {
12+
readonly #destroyRef = inject(DestroyRef);
1213
readonly #carouselService = inject(CarouselService);
1314
readonly #carouselState = inject(CarouselState);
1415

1516
items: (number | undefined)[] = [];
1617
active = 0;
17-
#carouselIndexSubscription?: Subscription;
1818

1919
ngOnInit(): void {
20-
this.carouselStateSubscribe();
21-
}
22-
23-
ngOnDestroy(): void {
24-
this.carouselStateSubscribe(false);
20+
this.#carouselService.carouselIndex$.pipe(takeUntilDestroyed(this.#destroyRef)).subscribe((nextIndex) => {
21+
this.items = this.#carouselState?.state?.items?.map((item) => item.index) ?? [];
22+
if ('active' in nextIndex) {
23+
this.active = nextIndex.active ?? 0;
24+
}
25+
});
2526
}
2627

2728
onClick(index: number): void {
@@ -30,17 +31,4 @@ export class CarouselIndicatorsComponent implements OnInit, OnDestroy {
3031
this.#carouselState.state = { direction, activeItemIndex: index };
3132
}
3233
}
33-
34-
private carouselStateSubscribe(subscribe: boolean = true): void {
35-
if (subscribe) {
36-
this.#carouselIndexSubscription = this.#carouselService.carouselIndex$.subscribe((nextIndex) => {
37-
this.items = this.#carouselState?.state?.items?.map((item) => item.index) ?? [];
38-
if ('active' in nextIndex) {
39-
this.active = nextIndex.active ?? 0;
40-
}
41-
});
42-
} else {
43-
this.#carouselIndexSubscription?.unsubscribe();
44-
}
45-
}
4634
}
+1-5Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
<div [@slideAnimation]="slide" [@.disabled]="!animate">
1+
<div [@slideAnimation]="slide()" [@.disabled]="!animate()">
22
<ng-content />
33
</div>
4-
<!--todo-->
5-
<!--<div [@fadeAnimation]="slide" [@.disabled]="!animate" >-->
6-
<!-- <ng-content />-->
7-
<!--</div>-->

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.