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 ae1aa1c

Browse filesBrowse files
committed
refactor(progress): signal inputs, host bindings, cleanup, tests, service introduction
1 parent 480611d commit ae1aa1c
Copy full SHA for ae1aa1c
Expand file treeCollapse file tree

10 files changed

+225
-160
lines changed

‎projects/coreui-angular/src/lib/progress/progress-bar.component.spec.ts

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/progress/progress-bar.component.spec.ts
+13-17Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
22

3+
import { ComponentRef } from '@angular/core';
34
import { ProgressBarComponent } from './progress-bar.component';
45
import { ProgressBarDirective } from './progress-bar.directive';
6+
import { ProgressService } from './progress.service';
57

68
describe('ProgressBarComponent', () => {
79
let component: ProgressBarComponent;
10+
let componentRef: ComponentRef<ProgressBarComponent>;
811
let fixture: ComponentFixture<ProgressBarComponent>;
12+
let directive: ProgressBarDirective;
913

1014
beforeEach(waitForAsync(() => {
1115
TestBed.configureTestingModule({
12-
imports: [ProgressBarComponent, ProgressBarDirective]
16+
imports: [ProgressBarComponent],
17+
providers: [ProgressService]
1318
}).compileComponents();
1419

1520
fixture = TestBed.createComponent(ProgressBarComponent);
1621

1722
component = fixture.componentInstance;
18-
fixture.debugElement.injector.get(ProgressBarDirective).value = 42;
19-
fixture.debugElement.injector.get(ProgressBarDirective).color = 'success';
20-
fixture.debugElement.injector.get(ProgressBarDirective).variant = 'striped';
21-
fixture.debugElement.injector.get(ProgressBarDirective).animated = true;
23+
componentRef = fixture.componentRef;
24+
directive = fixture.debugElement.injector.get(ProgressBarDirective);
25+
componentRef.setInput('value', 42);
26+
componentRef.setInput('color', 'success');
27+
componentRef.setInput('variant', 'striped');
28+
componentRef.setInput('animated', true);
2229
fixture.detectChanges();
2330
}));
2431

@@ -45,7 +52,7 @@ describe('ProgressBarComponent', () => {
4552
});
4653

4754
it('should not have aria-* attributes', () => {
48-
fixture.debugElement.injector.get(ProgressBarDirective).value = undefined;
55+
componentRef.setInput('value', undefined);
4956
fixture.detectChanges();
5057
expect(fixture.nativeElement.getAttribute('aria-valuenow')).toBeFalsy();
5158
expect(fixture.nativeElement.getAttribute('aria-valuemin')).toBeFalsy();
@@ -54,15 +61,4 @@ describe('ProgressBarComponent', () => {
5461
// expect(fixture.nativeElement.style.width).toBeFalsy();
5562
expect(fixture.nativeElement.style.width).toBe('0%');
5663
});
57-
58-
it('should not have aria-* attributes', () => {
59-
fixture.debugElement.injector.get(ProgressBarDirective).value = undefined;
60-
fixture.debugElement.injector.get(ProgressBarDirective).width = 84;
61-
fixture.detectChanges();
62-
expect(fixture.nativeElement.getAttribute('aria-valuenow')).toBeFalsy();
63-
expect(fixture.nativeElement.getAttribute('aria-valuemin')).toBeFalsy();
64-
expect(fixture.nativeElement.getAttribute('aria-valuemax')).toBeFalsy();
65-
expect(fixture.nativeElement.getAttribute('role')).toBeFalsy();
66-
expect(fixture.nativeElement.style.width).toBe('84%');
67-
});
6864
});
+9-11Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeDetectionStrategy, Component, HostBinding, inject } from '@angular/core';
1+
import { Component, computed, inject } from '@angular/core';
22
import { ProgressBarDirective } from './progress-bar.directive';
33

44
@Component({
@@ -7,25 +7,23 @@ import { ProgressBarDirective } from './progress-bar.directive';
77
hostDirectives: [
88
{
99
directive: ProgressBarDirective,
10-
inputs: ['animated', 'color', 'max', 'role', 'stacked', 'value', 'variant', 'width']
10+
inputs: ['animated', 'color', 'max', 'role', 'value', 'variant']
1111
}
1212
],
13-
changeDetection: ChangeDetectionStrategy.OnPush,
14-
host: { class: 'progress-bar' }
13+
host: { class: 'progress-bar', '[class]': 'hostClasses()' }
1514
})
1615
export class ProgressBarComponent {
1716
readonly #progressBarDirective: ProgressBarDirective | null = inject(ProgressBarDirective, { optional: true });
1817

19-
@HostBinding('class')
20-
get hostClasses(): Record<string, boolean> {
21-
const animated = this.#progressBarDirective?.animated;
22-
const color = this.#progressBarDirective?.color;
23-
const variant = this.#progressBarDirective?.variant;
18+
readonly hostClasses = computed(() => {
19+
const animated = this.#progressBarDirective?.animated();
20+
const color = this.#progressBarDirective?.color();
21+
const variant = this.#progressBarDirective?.variant();
2422
return {
2523
'progress-bar': true,
2624
'progress-bar-animated': !!animated,
2725
[`progress-bar-${variant}`]: !!variant,
2826
[`bg-${color}`]: !!color
29-
};
30-
}
27+
} as Record<string, boolean>;
28+
});
3129
}
+61-13Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,81 @@
1-
import { ElementRef, Renderer2 } from '@angular/core';
2-
import { TestBed } from '@angular/core/testing';
1+
import { Component, ComponentRef, DebugElement, ElementRef, input, Renderer2 } from '@angular/core';
2+
import { ComponentFixture, TestBed } from '@angular/core/testing';
33
import { ProgressBarDirective } from './progress-bar.directive';
4+
import { By } from '@angular/platform-browser';
5+
import { ProgressService } from './progress.service';
46

57
class MockElementRef extends ElementRef {}
68

9+
@Component({
10+
template: `<div cProgressBar [value]="value()" [color]="color()" variant="striped" animated></div>`,
11+
selector: 'c-test',
12+
imports: [ProgressBarDirective]
13+
})
14+
export class TestComponent {
15+
readonly value = input(42);
16+
readonly color = input('success');
17+
}
18+
719
describe('ProgressBarDirective', () => {
820
let directive: ProgressBarDirective;
21+
let debugElement: DebugElement;
22+
let fixture: ComponentFixture<TestComponent>;
23+
let componentRef: ComponentRef<TestComponent>;
924

1025
beforeEach(() => {
11-
1226
TestBed.configureTestingModule({
13-
providers: [
14-
Renderer2,
15-
{ provide: ElementRef, useClass: MockElementRef }
16-
]
27+
providers: [Renderer2, { provide: ElementRef, useClass: MockElementRef }, ProgressService],
28+
imports: [TestComponent]
1729
});
30+
fixture = TestBed.createComponent(TestComponent);
31+
componentRef = fixture.componentRef;
32+
debugElement = fixture.debugElement.query(By.directive(ProgressBarDirective));
33+
directive = debugElement.injector.get(ProgressBarDirective);
34+
fixture.detectChanges();
1835

19-
TestBed.runInInjectionContext(() => {
20-
directive = new ProgressBarDirective();
21-
});
36+
// TestBed.runInInjectionContext(() => {
37+
// directive = new ProgressBarDirective();
38+
// });
2239
});
2340

2441
it('should create an instance', () => {
2542
expect(directive).toBeDefined();
2643
});
2744

28-
it('should have percent value', () => {
29-
directive.value = 42;
30-
expect(directive.percent()).toBe(42);
45+
it('should have color value', () => {
46+
expect(directive.color()).toBe('success');
47+
});
48+
49+
it('should have max value', () => {
50+
expect(directive.max()).toBe(100);
51+
});
52+
53+
it('should have variant value', () => {
54+
expect(directive.variant()).toBe('striped');
3155
});
3256

57+
it('should have role value', () => {
58+
expect(directive.role()).toBe('progressbar');
59+
});
60+
61+
it('should have precision value', () => {
62+
expect(directive.precision()).toBe(0);
63+
});
64+
65+
it('should have animated value', () => {
66+
expect(directive.animated()).toBe(true);
67+
});
68+
69+
it('should have aria-* attributes', () => {
70+
expect(debugElement.nativeElement.getAttribute('aria-valuenow')).toBe('42');
71+
expect(debugElement.nativeElement.getAttribute('aria-valuemin')).toBe('0');
72+
expect(debugElement.nativeElement.getAttribute('aria-valuemax')).toBe('100');
73+
expect(debugElement.nativeElement.getAttribute('role')).toBe('progressbar');
74+
componentRef.setInput('value', undefined);
75+
fixture.detectChanges();
76+
expect(debugElement.nativeElement.getAttribute('aria-valuenow')).toBeNull();
77+
expect(debugElement.nativeElement.getAttribute('aria-valuemin')).toBeNull();
78+
expect(debugElement.nativeElement.getAttribute('aria-valuemax')).toBeNull();
79+
expect(debugElement.nativeElement.getAttribute('role')).toBeNull();
80+
});
3381
});
+37-63Lines changed: 37 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,96 @@
11
import {
22
booleanAttribute,
3-
computed,
43
Directive,
54
effect,
65
EffectRef,
76
ElementRef,
87
inject,
9-
Input,
8+
input,
109
numberAttribute,
11-
Renderer2,
12-
signal,
13-
WritableSignal
10+
Renderer2
1411
} from '@angular/core';
1512
import { Colors } from '../coreui.types';
16-
import { IProgressBar } from './progress.type';
13+
import { ProgressService } from './progress.service';
1714

1815
@Directive({
19-
selector: '[cProgressBar]'
16+
selector: '[cProgressBar]',
17+
exportAs: 'cProgressBar'
2018
})
21-
export class ProgressBarDirective implements IProgressBar {
19+
export class ProgressBarDirective {
2220
readonly #renderer = inject(Renderer2);
2321
readonly #hostElement = inject(ElementRef);
24-
25-
readonly #max = signal(100);
26-
readonly #min = 0;
27-
readonly #value: WritableSignal<number | undefined> = signal(undefined);
28-
readonly #width: WritableSignal<number | undefined> = signal(undefined);
29-
30-
readonly percent = computed(() => {
31-
return +((((this.#value() ?? this.#width() ?? 0) - this.#min) / (this.#max() - this.#min)) * 100).toFixed(
32-
this.precision
33-
);
34-
});
22+
readonly #progressService = inject(ProgressService);
3523

3624
readonly #valuesEffect: EffectRef = effect(() => {
3725
const host: HTMLElement = this.#hostElement.nativeElement;
38-
if (this.#value() === undefined || this.#width()) {
26+
const value = this.#progressService.value();
27+
const percent = this.#progressService.percent();
28+
const stacked = this.#progressService.stacked();
29+
if (value === undefined) {
3930
for (const name of ['aria-valuenow', 'aria-valuemax', 'aria-valuemin', 'role']) {
4031
this.#renderer.removeAttribute(host, name);
4132
}
4233
} else {
43-
this.#renderer.setAttribute(host, 'aria-valuenow', String(this.#value()));
44-
this.#renderer.setAttribute(host, 'aria-valuemin', String(this.#min));
45-
this.#renderer.setAttribute(host, 'aria-valuemax', String(this.#max()));
46-
this.#renderer.setAttribute(host, 'role', this.role);
34+
const { min, max } = this.#progressService;
35+
this.#renderer.setAttribute(host, 'aria-valuenow', String(value));
36+
this.#renderer.setAttribute(host, 'aria-valuemin', String(min()));
37+
this.#renderer.setAttribute(host, 'aria-valuemax', String(max()));
38+
this.#renderer.setAttribute(host, 'role', this.role());
4739
}
4840
const tagName = host.tagName;
49-
if (
50-
this.percent() >= 0 &&
51-
((this.stacked && tagName === 'C-PROGRESS') || (!this.stacked && tagName !== 'C-PROGRESS'))
52-
) {
53-
this.#renderer.setStyle(host, 'width', `${this.percent()}%`);
41+
if (percent >= 0 && ((stacked && tagName === 'C-PROGRESS') || (!stacked && tagName !== 'C-PROGRESS'))) {
42+
this.#renderer.setStyle(host, 'width', `${percent}%`);
5443
} else {
5544
this.#renderer.removeStyle(host, 'width');
5645
}
5746
});
5847

5948
/**
6049
* Use to animate the stripes right to left via CSS3 animations.
61-
* @type boolean
50+
* @return boolean
6251
*/
63-
@Input({ transform: booleanAttribute }) animated?: boolean;
52+
readonly animated = input<boolean, unknown>(undefined, { transform: booleanAttribute });
6453

6554
/**
6655
* Sets the color context of the component to one of CoreUI’s themed colors.
6756
* @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light'
6857
*/
69-
@Input() color?: Colors;
58+
readonly color = input<Colors>();
7059

71-
// TODO: check if this is necessary.
72-
@Input({ transform: numberAttribute }) precision: number = 0;
60+
readonly precision = input(0, { transform: numberAttribute });
7361

7462
/**
7563
* The percent value the ProgressBar.
76-
* @type number
64+
* @return number
7765
* @default 0
7866
*/
79-
@Input({ transform: numberAttribute })
80-
set value(value: number | undefined) {
81-
this.#value.set(value);
82-
}
83-
84-
get value() {
85-
return this.#value();
86-
}
87-
88-
@Input({ transform: numberAttribute })
89-
set width(value: number | undefined) {
90-
this.#width.set(value);
91-
}
67+
readonly value = input(undefined, { transform: numberAttribute });
9268

9369
/**
9470
* Set the progress bar variant to optional striped.
9571
* @values 'striped'
9672
* @default undefined
9773
*/
98-
@Input() variant?: 'striped';
74+
readonly variant = input<'striped'>();
9975

10076
/**
10177
* The max value of the ProgressBar.
102-
* @type number
78+
* @return number
10379
* @default 100
10480
*/
105-
@Input({ transform: numberAttribute })
106-
set max(max: number) {
107-
this.#max.set(isNaN(max) || max <= 0 ? 100 : max);
108-
}
109-
110-
/**
111-
* Stacked ProgressBars.
112-
* @type boolean
113-
* @default false
114-
*/
115-
@Input({ transform: booleanAttribute }) stacked?: boolean = false;
81+
readonly max = input(100, { transform: numberAttribute });
11682

11783
/**
11884
* Set default html role attribute.
119-
* @type string
85+
* @return string
12086
*/
121-
@Input() role: string = 'progressbar';
87+
readonly role = input<string>('progressbar');
88+
89+
readonly #serviceEffect = effect(() => {
90+
this.#progressService.precision.set(this.precision());
91+
const max = this.max();
92+
this.#progressService.max.set(isNaN(max) || max <= 0 ? 100 : max);
93+
const value = this.value();
94+
this.#progressService.value.set(value && !isNaN(value) ? value : undefined);
95+
});
12296
}
+5-7Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
import { ChangeDetectionStrategy, Component, HostBinding, Input } from '@angular/core';
2-
import { IProgressBarStacked } from './progress.type';
1+
import { Component, input } from '@angular/core';
32

43
@Component({
54
selector: 'c-progress-stacked',
5+
exportAs: 'cProgressStacked',
66
template: '<ng-content />',
77
styles: `
88
:host {
99
display: flex;
1010
}
1111
`,
12-
changeDetection: ChangeDetectionStrategy.OnPush
12+
host: { '[class.progress-stacked]': 'stacked()' }
1313
})
14-
export class ProgressStackedComponent implements IProgressBarStacked {
15-
@Input()
16-
@HostBinding('class.progress-stacked')
17-
stacked = true;
14+
export class ProgressStackedComponent {
15+
readonly stacked = input(true);
1816
}

‎projects/coreui-angular/src/lib/progress/progress.component.html

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/progress/progress.component.html
+3-6Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
@if (contentProgressBars.length) {
1+
@if (contentProgressBars()?.length) {
22
<ng-container *ngTemplateOutlet="defaultContent" />
3-
} @else if (pbd?.stacked) {
4-
<c-progress-bar [animated]="pbd?.animated" [variant]="pbd?.variant" [color]="pbd?.color" stacked>
5-
<ng-container *ngTemplateOutlet="defaultContent" />
6-
</c-progress-bar>
73
} @else {
8-
<c-progress-bar [width]="pbd?.percent()" [animated]="pbd?.animated" [variant]="pbd?.variant" [color]="pbd?.color">
4+
@let pbd = progressBarDirective;
5+
<c-progress-bar [animated]="pbd?.animated()" [variant]="pbd?.variant()" [color]="pbd?.color()" [value]="value()">
96
<ng-container *ngTemplateOutlet="defaultContent" />
107
</c-progress-bar>
118
}

0 commit comments

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