Documentation Index
Fetch the complete documentation index at: https://mintlify.com/AndresOrozcoDev/Paginator/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The LoadingService manages the application’s loading state, tracking active HTTP requests and providing an observable stream that components can subscribe to. It works in conjunction with the LoadingInterceptor to automatically show and hide loading indicators.
Location: src/app/core/services/loading.service.ts
Properties
loading$
public readonly loading$: Observable<boolean>
Public observable that emits true when loading is active and false when all requests are complete. Components subscribe to this to show/hide loading indicators.
requestsInFlight
private requestsInFlight: number = 0
Private counter tracking the number of concurrent HTTP requests in progress.
Methods
show()
Increments the request counter and emits a loading state of true.
Signature:
Behavior:
- Increments
requestsInFlight by 1
- Emits
true to the loading$ observable
- Called automatically by the
LoadingInterceptor when an HTTP request starts
hide()
Decrements the request counter and emits false only when all requests are complete.
Signature:
Behavior:
- Decrements
requestsInFlight by 1 (minimum value is 0)
- Only emits
false to loading$ when requestsInFlight reaches 0
- Called automatically by the
LoadingInterceptor when an HTTP request completes
Usage in Components
Basic Loading Indicator
import { Component } from '@angular/core';
import { LoadingService } from './core/services/loading.service';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-loading-spinner',
standalone: true,
imports: [CommonModule],
template: `
<div *ngIf="loadingService.loading$ | async" class="spinner-overlay">
<div class="spinner"></div>
<p>Loading...</p>
</div>
`,
styles: [`
.spinner-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`]
})
export class LoadingSpinnerComponent {
constructor(public loadingService: LoadingService) {}
}
Using Loading State in Component Logic
import { Component, OnInit, OnDestroy } from '@angular/core';
import { LoadingService } from './core/services/loading.service';
import { Subject, takeUntil } from 'rxjs';
@Component({
selector: 'app-data-table',
templateUrl: './data-table.component.html'
})
export class DataTableComponent implements OnInit, OnDestroy {
isLoading = false;
private destroy$ = new Subject<void>();
constructor(private loadingService: LoadingService) {}
ngOnInit(): void {
this.loadingService.loading$
.pipe(takeUntil(this.destroy$))
.subscribe(isLoading => {
this.isLoading = isLoading;
console.log('Loading state changed:', isLoading);
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
Integration with LoadingInterceptor
The LoadingService is automatically integrated with HTTP requests through the LoadingInterceptor.
Interceptor Location: src/app/core/interceptors/loading.interceptor.ts
How It Works
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { LoadingService } from '../services/loading.service';
import { finalize } from 'rxjs';
export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
const loadingService = inject(LoadingService);
loadingService.show();
return next(req).pipe(
finalize(() => loadingService.hide())
);
};
Flow:
- When an HTTP request starts, the interceptor calls
loadingService.show()
- The request counter increments and
loading$ emits true
- When the request completes (success or error),
finalize() calls loadingService.hide()
- The request counter decrements
- When all requests are done (counter reaches 0),
loading$ emits false
Registering the Interceptor
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { loadingInterceptor } from './core/interceptors/loading.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([loadingInterceptor])
)
]
};
Observable Pattern
The service uses RxJS BehaviorSubject to manage state:
private _loading = new BehaviorSubject<boolean>(false);
public readonly loading$ = this._loading.asObservable();
Benefits:
BehaviorSubject provides the current value immediately to new subscribers
- Initial state is
false (not loading)
- Private
_loading subject prevents external code from directly emitting values
- Public
loading$ observable allows components to subscribe safely
Handling Multiple Concurrent Requests
The service correctly handles multiple simultaneous HTTP requests:
// Request 1 starts: requestsInFlight = 1, loading$ emits true
// Request 2 starts: requestsInFlight = 2, loading$ stays true
// Request 1 ends: requestsInFlight = 1, loading$ stays true
// Request 2 ends: requestsInFlight = 0, loading$ emits false
The hide() method uses Math.max() to prevent negative values:
this.requestsInFlight = Math.max(this.requestsInFlight - 1, 0);
Complete Example
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoadingService } from './core/services/loading.service';
import { LocationsService } from './features/paginator/services/locations.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
template: `
<div class="app">
<!-- Loading overlay automatically shown during HTTP requests -->
<div *ngIf="loadingService.loading$ | async" class="loading-overlay">
<div class="spinner"></div>
</div>
<!-- Main content -->
<div class="content">
<button (click)="loadData()">Load Data</button>
<div *ngIf="data.length > 0">
<p *ngFor="let item of data">{{ item.state }}</p>
</div>
</div>
</div>
`
})
export class AppComponent {
data: any[] = [];
constructor(
public loadingService: LoadingService,
private locationsService: LocationsService
) {}
loadData(): void {
// Loading indicator automatically shown
this.locationsService.getStates().subscribe({
next: (response) => {
this.data = response.data;
// Loading indicator automatically hidden
},
error: (error) => {
console.error('Error:', error);
// Loading indicator automatically hidden even on error
}
});
}
}
Implementation Details
- The service is provided in the root injector (
providedIn: 'root')
- Uses RxJS
BehaviorSubject for reactive state management
- Automatically integrates with all HTTP requests via the interceptor
- Handles concurrent requests correctly with a counter
- No manual intervention needed - works automatically with
HttpClient