Universal Api caller module for Angular 7-9 with NGRX
Author: deejayy, wagnerp, 2019-07-01, updated: 2020-03-29
One word: boilerplate
Calling API endpoints is a regular task for front end developers who make SPAs. Keeping this process in order, many of us are using a state manager tool (eg. NGRX).
If you want to stick to official documentation and best practices, you have to create a store (actions, reducer, effects, selectors) for every different endpoint and maintain some basic states:
- is the request sent already? (loading = true)
- what is the outcome? (success or some error?)
- what is the response? (data or error object)
If I say NGRX, you could think about boilerplate. And not just because creating a simple state requires a lot of predefined code, but you have to do this for every API call. Things can easily run out of your hands.
Proposal
I struggled with the same for the last few months and put together a possible solution which streamlines the way we used to call API endpoints: a universal API caller module.
What is it providing?
You can send an HTTP request with one line of code:
... and you can access all the interesting states (loding, error, data, etc) with another:
And an extra: you can choose to not send a request when there is a response already from an earlier request (cache!) or is underway.
How is it built up?
Requirements
- NGRX
- Immer
- an auth service (for the Bearer token and deauth process)
- a config service (for the api endpoint)
Structure
The ApiModule is a simple Angular module you can import in your modules. The main component is built around a state with the usual files:
- state
- actions
- effects
- selectors
- reducer
- facade
Maybe you want to check out facades and immer before reading further:
- Facades: https://medium.com/@thomasburlesonIA/ngrx-facades-better-state-management-82a04b9a1e39
- Immer: https://blog.angularindepth.com/clean-ngrx-reducers-using-immer-7fe4a0d43508
There is a service which takes care of sending the actual HttpClient request. Note: this service will require an auth service to properly send request which needs authorization (as the source of the Bearer token).
The state will hold the following properties:
- loading: the request is sent, but not yet finished
- success: is the request succeeded?
- data: holds the response on success
- error: did we run into some issue, like an HTTP 404?
- errorData: holds the error response on failure
How to use
Like I mentioned, one line for sending a request, one line for accessing the response. Sounds pretty good, right? I intentionally forgot to mention a small thing: you have to keep a catalog of API requests for smooth working, but I assume you already have something like that.
Catalog
So, let's create an API catalog file my-module-api-catalog.ts
with the following content:
The ApiCallItem
interface defines the following properties:
- url: endpoint url, without the domain but starting with a slash. Domain should be defined in the environment (or check the effects file to adjust)
- auth?: does it require authorization? If yes, the api service will request the token information from the user facade
- data?: request payload, a JSON object containing the request parameters
- useExisting?: check the existing response before sending the request and if there is something in the state then don't do anything
You can use hashtags in URLs, since they won't be sent to backend, but it is useful when you want to ask the same endpoint and you need to separate the state from each other.
If data
property is empty, it will be a GET request, else a POST.
useExisting
can be defined as a property as well, the interface will accept that.
Sending the request
As a GET request:
As a POST request:
Accessing the results
We are talking about NGRX, so everything is accessible via streams.
Note: you don't need to pass payload to this object, it is ignored.
With this line of code you'll have an object with the state properties as streams. You can access them like this:
... or from template:
There is a lot of room to improve, enhance the module, feel free to fork/contribute to it on github.