Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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