Skip to content

Commit

Permalink
Merge pull request #372 from MurhafSousli/release/12.0.1
Browse files Browse the repository at this point in the history
v12.0.1
  • Loading branch information
MurhafSousli authored Aug 24, 2024
2 parents 149f0e2 + b0525c6 commit 63a8154
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 49 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

## 12.0.0
## 12.0.1

- Upgrade to Angular 18 (still compatible v17.3.0 and above).
- feat: Introduce CSS variables for more flexible customization, see [styling](https://github.com/MurhafSousli/ngx-progressbar/wiki/styling).
Expand Down
4 changes: 2 additions & 2 deletions projects/ngx-progressbar-demo/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<div class="container">

<app-header></app-header>
<app-header/>

<div class="menu">
<a routerLink="/" routerLinkActive="active-link" [routerLinkActiveOptions]="{ exact: true }"> Basic </a> /
<a routerLink="/custom" routerLinkActive="active-link"> Custom </a>
</div>

<div class="main">
<router-outlet></router-outlet>
<router-outlet/>
</div>
</div>

Expand Down
31 changes: 14 additions & 17 deletions projects/ngx-progressbar-demo/src/app/home/lab/lab.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
<span class="input-name">direction</span> =
<i class="input">
<select name="direction" [(ngModel)]="options.direction">
<option *ngFor="let d of directions" [ngValue]="d">{{ d }}</option>
@for (d of directions; track d) {
<option [ngValue]="d">{{ d }}</option>
}
</select>
</i>
</span>
Expand All @@ -16,11 +18,13 @@
</span>

<span class="input-wrapper">
<span class="input-name">trickleSpeed</span> = <i class="input"><input name="trickleSpeed" [(ngModel)]="options.trickleSpeed"></i>
<span class="input-name">trickleSpeed</span> = <i class="input"><input name="trickleSpeed"
[(ngModel)]="options.trickleSpeed"></i>
</span>

<span class="input-wrapper">
<span class="input-name">debounceTime</span> = <i class="input"><input name="debounceTime" [(ngModel)]="options.debounceTime"></i>
<span class="input-name">debounceTime</span> = <i class="input"><input name="debounceTime"
[(ngModel)]="options.debounceTime"></i>
</span>

<span class="input-wrapper">
Expand All @@ -40,18 +44,13 @@
<span class="input-wrapper">
<span class="input-name">spinnerPosition</span> = <i class="input">
<select name="spinnerPosition" [(ngModel)]="options.spinnerPosition">
<option *ngFor="let p of spinnerPosition" [ngValue]="p">{{ p }}</option>
@for (p of spinnerPosition; track p) {
<option [ngValue]="p">{{ p }}</option>
}
</select>
</i>
</span>

<!-- <span class="input-wrapper">-->
<!-- <span class="input-name">thick</span> = <i class="input">-->
<!-- <input name="thick" type="checkbox" [(ngModel)]="options.thick">-->
<!-- <span>{{ options.thick }}</span>-->
<!-- </i>-->
<!-- </span>-->

<span class="input-wrapper">
<span class="input-name">relative</span> = <i class="input">
<input name="relative" type="checkbox" [(ngModel)]="options.relative">
Expand All @@ -60,17 +59,15 @@
</span>

<span class="input-wrapper">
<span class="input-name">min</span> = <i class="input"><input type="number" name="min" min="0" max="99" [(ngModel)]="options.min"></i>
<span class="input-name">min</span> = <i class="input"><input type="number" name="min" min="0" max="99"
[(ngModel)]="options.min"></i>
</span>

<span class="input-wrapper">
<span class="input-name">max</span> = <i class="input"><input type="number" name="max" min="1" max="100" [(ngModel)]="options.max"></i>
<span class="input-name">max</span> = <i class="input"><input type="number" name="max" min="1" max="100"
[(ngModel)]="options.max"></i>
</span>

<!-- <span class="input-wrapper">-->
<!-- <span class="input-name">color</span> = <i class="input"><input name="color" [(ngModel)]="options.color"></i>-->
<!-- </span>-->

<span class="output-wrapper">
<span class="output-name">started</span> = <i class="output">onStarted()</i>
</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgProgressOptions } from 'ngx-progressbar';

Expand All @@ -9,23 +8,23 @@ import { NgProgressOptions } from 'ngx-progressbar';
templateUrl: './lab.component.html',
styleUrls: ['./lab.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [FormsModule, CommonModule]
imports: [FormsModule]
})
export class LabComponent {

directions = [
directions: string[] = [
'ltr+',
'ltr-',
'rtl+',
'rtl-'
];

spinnerPosition = [
spinnerPosition: string[] = [
'right',
'left'
];

@Input() options: NgProgressOptions = {};
@Output() optionsChange = new EventEmitter(true);
@Output() optionsChange: EventEmitter<boolean> = new EventEmitter(true);

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class NgProgressHttpBase {
effect(() => {
if (this.manager.requestsLoading()) {
this.progressRef.start();
} else {
} else if (this.progressRef.isActive) {
this.progressRef.complete();
}
}, { allowSignalWrites: true });
Expand Down
2 changes: 1 addition & 1 deletion projects/ngx-progressbar/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-progressbar",
"version": "12.0.0",
"version": "12.0.1",
"author": {
"name": "Murhaf Sousli",
"url": "https://github.com/MurhafSousli",
Expand Down
59 changes: 43 additions & 16 deletions projects/ngx-progressbar/src/lib/ng-progress-ref.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { inject, signal, effect, computed, Directive, OnDestroy, Signal, WritableSignal, EffectCleanupRegisterFn } from '@angular/core';
import {
inject,
signal,
effect,
computed,
Directive,
OnDestroy,
Signal,
WritableSignal,
EffectCleanupRegisterFn
} from '@angular/core';
import {
Observable,
Subject,
Subscription,
BehaviorSubject,
of,
tap,
delay,
Expand All @@ -14,6 +25,11 @@ import {
} from 'rxjs';
import { NgProgressOptions, NG_PROGRESS_OPTIONS } from './ng-progress.model';

enum TriggerType {
START = 'START',
COMPLETE = 'COMPLETE'
}

@Directive({
standalone: true,
selector: '[ngProgressRef]',
Expand All @@ -27,15 +43,21 @@ export class NgProgressRef implements OnDestroy {

private _active: WritableSignal<boolean> = signal<boolean>(false);

active: Signal<boolean> = computed(() => this._active());
// A boolean flag that indicates the active state
isActive: boolean;

active: Signal<boolean> = computed(() => {
this.isActive = this._active();
return this.isActive;
});

progress: Signal<number> = computed(() => this._progress());

config: Signal<NgProgressOptions> = computed(() => this._config());

private readonly _trigger: WritableSignal<boolean> = signal<boolean>(false);
private _trigger: BehaviorSubject<TriggerType> = new BehaviorSubject<TriggerType>(null);

// Progress start source event (used to cancel finalizing delays)
// Progress start source event (used to cancel onComplete delays)
private readonly _started: Subject<void> = new Subject<void>();
readonly started: Observable<void> = this._started.asObservable();

Expand All @@ -54,20 +76,23 @@ export class NgProgressRef implements OnDestroy {
effect((onCleanup: EffectCleanupRegisterFn) => {
sub$?.unsubscribe();

if (this._trigger()) {
sub$ = timer(this.config().debounceTime).pipe(
switchMap(() => this.onTrickling(this.config()))
).subscribe();
} else {
setTimeout(() => {
sub$ = this.onComplete(this.config()).subscribe();
});
}
sub$ = this._trigger.pipe(
switchMap((trigger: TriggerType) => {
if (trigger === TriggerType.START) {
return timer(this.config().debounceTime).pipe(
switchMap(() => this.onTrickling(this.config()))
);
} else if (trigger === TriggerType.COMPLETE) {
return this.onComplete(this.config());
}
return EMPTY;
})
).subscribe();

onCleanup(() => {
sub$?.unsubscribe();
});
});
}, { allowSignalWrites: true });
}

ngOnDestroy(): void {
Expand All @@ -80,15 +105,15 @@ export class NgProgressRef implements OnDestroy {
*/
start(): void {
this._started.next();
this._trigger.set(true);
this._trigger.next(TriggerType.START);
this._active.set(true);
}

/**
* Complete the progress
*/
complete(): void {
this._trigger.set(false);
this._trigger.next(TriggerType.COMPLETE);
}

/**
Expand All @@ -110,6 +135,8 @@ export class NgProgressRef implements OnDestroy {
* Set the progress
*/
set(n: number): void {
// this._trigger.set(TriggerType.SET);
this._active.set(true);
this._progress.set(this.clamp(n));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ describe(`NgProgressHttp`, () => {
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);

const startSpy: jasmine.Spy = spyOn(progressRef, 'start');
const startSpy: jasmine.Spy = spyOn(progressRef, 'start').and.callThrough();
const completeSpy: jasmine.Spy = spyOn(progressRef, 'complete');

httpClient.get('/users').subscribe(() => {
// Check that progress.complete() has been called after the request is completed
// Need to check that async
setTimeout(() => {
// Check that progress.complete() has been called after the request is completed
expect(completeSpy).toHaveBeenCalled();
done();
});
});

// Need to detect changes
fixture.detectChanges();

// Check that progress.start() has been called
Expand All @@ -60,7 +60,7 @@ describe(`NgProgressHttp`, () => {
const req: TestRequest = httpTestingController.expectOne('/users');
expect(req.request.method).toEqual('GET');

// Complete the request after a tiny delay
// Complete the request after 200ms delay
setTimeout(() => {
req.flush({});
}, 200);
Expand Down
23 changes: 21 additions & 2 deletions projects/ngx-progressbar/src/lib/tests/ng-progress-ref.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('NgProgressRef', () => {
expect(directive.active()).toBeFalse();
});

it('should start and complete the progress', (done: DoneFn) => {
it('should call continuously call set() on trickling', (done: DoneFn) => {
const setSpy: jasmine.Spy = spyOn(directive, 'set');
// Assume active state is off
directive['_active'].set(false);
Expand All @@ -57,6 +57,18 @@ describe('NgProgressRef', () => {
});
});

it('should set the progress even if it has not started', async () => {
// Assume active state is off
directive.set(40);
fixture.detectChanges();
expect(directive.progress()).toBe(40);
expect(directive.active()).toBeTrue();

directive.complete();
fixture.detectChanges();
await afterTimeout(350);
expect(directive.active()).toBeFalse();
});

it('should increment the progress when inc function is called', async () => {
directive.inc();
Expand Down Expand Up @@ -94,13 +106,20 @@ describe('NgProgressRef', () => {
directive.setConfig(newConfig);
expect(directive.config()).toEqual(newConfig);
});

it('should not do anything if complete() is called when progress has not started', () => {
const completeSpy: jasmine.Spy = spyOn(directive, 'complete').and.callThrough();
directive.complete();
fixture.detectChanges();
expect(completeSpy).toHaveBeenCalled();
});
});

@Component({
standalone: true,
imports: [NgProgressRef],
template: `
<div ngProgressRef></div>
<div ngProgressRef></div>
`
})
class TestComponent {
Expand Down

1 comment on commit 63a8154

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.