The HTTPManagerService is a collection of 4 services for HTTP and Local Storage management.
npm install http-request-manager
Http service simplifies http requests in the following ways
- Define a fixed endpoint service
- Provide a fixed set of headers or add to the headers on request
- Proxy Config Support
- Adapter for strict type checking for incoming data
- Mapper for data transformation for outgoing data
- Polling requests automatically
- Retry requests if failure
- Stream http requests from endpoint service
- Display Error provides a Toast (Snackbar) notification on error
Http service simplifies http request state management in the following ways
- Inherits from HTTP Manager Service for config options
- Creates a Component Store state (NGRX)
- Selectors for data and selecting data records
- Create, Update and Delete automatically updates data by endpoint and state on success
- Get keeps state of records
- Inherits from HTTP State Manager Service for config options
- Manages HTTP Requests caching data in local DB (IndexedDB) using DixieJS
- Requests check first local DB and if data is not stale, request is made and updates DB
- Expiry time/date can be set for DB records for caching
- Allows for Local and Storage management for data Persistence
- Component Store (NGRX) State for changes on storage
- Encryption for data
- Expiry time/date can be set for stores
There are three ways to use the service, depending on your needs:
httpManagerService = inject(HTTPManagerService)
-
This approach provides a singleton state for the service, as services are provided in root (@Injectable({ providedIn: 'root' })).
-
Any components or services that inject this service will share the same state, meaning they all have access to:
- data$ (observable for data)
- isPending$ (loading state)
- countDown$ (loading state)
- error$ (error state)
- Methods associated with the service
- Since there is only one shared state, it can be reused across components without creating new instances.
Here is a very simple example
//Define your API request details using the `ApiRequest` adapter const apiOptions = ApiRequest.adapt({ server: 'myApiUrl/rest/clients' // or ['myApiUrl', 'rest', 'clients'] }) onFetchData() { this.httpManagerService.getRequest<any>(apiOptions) .pipe( catchError(error => { return throwError(() => this.errorHandling(error, 'GET')) }) ).subscribe(data => console.log(data))) }
In this example, we define the endpoint URL as myApiUrl. This will return the data from the observable for your use case.
The endpoint can be:
A direct URL (e.g., https://apple.com/clients).
A proxy-config file (recommended to avoid CORS issues).
A string, an array of strings, or numbers—similar to Angular’s Router.navigate() API.
The HTTP Service Manager automatically sanitizes the endpoint by removing redundant /
characters and validating the URL.
You can also add query parameters by passing an object:
{ sortBy: 'asc', filter: ['samples', 'testing'] }
This translates to: https://apple.com/clients?sortBy=asc&filter=samples,testing
There are a number of other options available in the ApiRequest
adapter
This approach creates a new instance of the service for each component or service that extends it, ensuring each instance maintains its own independent state.
-
Similar to the first approach, instead of one instance of the state you are creating a new state instance
-
CRUD operations return Observables, similar to HttpClient.
-
This allows for custom error handling and executing additional actions after the request completes.
-
Since HTTP requests are asynchronous, you must subscribe to the Observable to receive data:
myService.getData().subscribe(response => { console.log(response); });
-
Alternatively, you can use the async pipe for automatic subscription and cleanup:
{{ myData$ | async }}
First we need to extend the Component or Service (recommended approach) with the HTTPManagerService
Component Example:
export class RequestManagerDemoComponent extends HTTPManagerService<any> implements OnInit {
constructor() {
super()
}
}
Service Example:
@Injectable({
providedIn: 'root'
})
export class myAPIService extends HTTPManagerService<any> {
constructor() {
super()
}
}
In this example, we define the endpoint URL as myApiUrl. This will return the data from the observable for your use case.
The endpoint can be:
A direct URL (e.g., https://apple.com/clients).
A proxy-config file (recommended to avoid CORS issues).
A string, an array of strings, or numbers—similar to Angular’s Router.navigate() API.
The HTTP Service Manager automatically sanitizes the endpoint by removing redundant /
characters and validating the URL.
You can also add query parameters by passing an object:
{ sortBy: 'asc', filter: ['samples', 'testing'] }
This translates to: https://apple.com/clients?sortBy=asc&filter=samples,testing
Lets define these query params as a variable called PARAMS
and use them in the following request.
There are a number of other options available in the ApiRequest
adapter
No we can define a method:
In this example take note that the clientID has been passed to the method and the param after the apiOptions allows for further customization for the api request being made. In this case we extended the the base path defined in the ApiRequest
options. We also added the PARAMS
for query parameters.
Define your API request options using the 'ApiRequest' adapter
const apiOptions = ApiRequest.adapt({
server: ['myApiUrl', 'rest', 'clients']
})
// Dynamically change these params
const PARAMS = {
sortBy: 'asc',
filter: ['samples', 'testing']
}
onFetchData(clientId) {
this.httpManagerService.getRequest<any>(apiOptions, [clientId, this.PARAMS])
.pipe(
catchError(error => {
return throwError(() => this.errorHandling(error, 'GET'))
})
).subscribe(data => console.log(data)))
}
- When executing CRUD operations, their results are automatically pushed into the data$ observable within the service.
- This means you can bind the data$ observable to a component to automatically update UI elements.
- Using this approach enables state management across components and allows data refresh actions without needing additional logic.
First we need to extend the Component or Service (recommended approach) with the HTTPManagerService
Component Example:
The structure would be: Component extends HTTPManagerService
export class RequestManagerDemoComponent extends HTTPManagerService<any> implements OnInit {
constructor() {
super()
}
}
Service Example:
The structure would be: Component injects a service call clientsService
and clientsService
extends HTTPManagerService
@Injectable({
providedIn: 'root'
})
export class myAPIService extends HTTPManagerService<any> {
constructor() {
super()
}
}
In this example, we define the endpoint URL as myApiUrl.
Lets define these query params as a variable called PARAMS
and use them in the following request.
No we can define a method:
In this example the clientID has been passed to the method.
We also added the PARAMS
for query parameters.
Define your API request options using the 'ApiRequest' adapter
const apiOptions = ApiRequest.adapt({
server: ['myApiUrl', 'rest', 'clients']
})
// Dynamically change these params
const PARAMS = {
sortBy: 'asc',
filter: ['samples', 'testing']
}
onFetchData(clientId: number) {
this.httpManagerService.getRequest<any>(apiOptions, [clientId, this.PARAMS]).subscribe()
}
In the above case we are not using the data returning from the Observable but rather executing the api call, like a refresh if called again
To access the data from the observable, you need to bind the observable if your using a component that injects the service that extends the HTTPManagerService
In the component where the service was injected, we define
data$ = this.clientsService.data$
isPending$ = this.clientsService.isPending$
error$ = this.clientsService.error$
By doing this gives your component the state of the data and other feedback states for the request that now you can use in your template.
{{ data$ | async }}
{{ isPending$ | async }}
{{ error$ | async }}
The following are the available options when configuring an ApiRequest object:
apiRequest = ApiRequest.adapt({
server: string,
path: any[],
headers: any,
adapter?: any,
mapper?: any,
polling?: number, // in seconds (undefined | 0 = none)
retry: RetryOptions,
stream?: boolean
displayError: boolean
});
Property | Description | Type | Required |
---|---|---|---|
server |
The base URL or service endpoint for the API request. | string |
✅ |
path |
Additional paths to append to the base URL for constructing the full API endpoint. | any[] |
✅ |
headers |
Custom headers to include in the request, provided as an object. | any |
✅ |
adapter |
A model adapter used to transform incoming data (e.g., DistrictData.adapt ). |
any |
❌ |
mapper |
A model adapter used to map outgoing data before sending it to the server (e.g., DistrictData.mapper ). |
any |
❌ |
polling |
Enables periodic polling, where the request will be made every specified number of seconds. |
number (seconds) |
❌ |
retry |
Retry logic using RetryOptions , which includes: - times : Number of retry attempts. - delay : Delay in milliseconds between retries. |
RetryOptions |
✅ |
stream |
A flag to indicate whether the request expects a stream of data from the server. | boolean |
❌ |
displayError |
A flag to indicate whether to present the error from the request in a snackbar notification. | boolean |
✅ |
- countdown$: If polling is active, this property gives feedback on when the next request will take place. It starts from the specified time in seconds and counts down to 0, triggering the next request and restarting the countdown.
- error$: This returns any HTTP error that occurs during the request.
- isPending$: This boolean value indicates whether the request is pending (true) or has been completed (false).
- data$: You can access the data fetched in the request using the data$ observable. This provides the response from the server, which can be used for further processing or displaying in the UI.
There are 3 interceptors that you can import into your project for api requests.
You may add these interceptors in your AppModule
file as providers and import them into as providers.
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: WithCredentialsInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: RequestHeadersInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: RequestErrorInterceptor, multi: true }
],
Or you can use all intercepts above by importing the HttpRequestManagerModule
in your AppModule
- RequestErrorInterceptor
This interceptor handles errors of type 400 and 500. This interceptor is applicable when you need to display UI for the user to indicate a server or request error has occurred. The Status and Error Text is displayed in a toast-message
modal.
-
WithCredentialsInterceptor
Adds to your request header
{ credentials: true }
-
RequestHeadersInterceptor
Adds to your request - current local
language
(i18n) selection andcurrent date
to every request{ 'Content-Type': 'application/json', 'Accept-Language': this.language || 'en-CA', 'Current-Date': this.currentDate }