A Generic HTTP Service Approach for Angular Applications
Part of the project I’m currently working on is building an Angular 4-based web application, which pulls and visualizes data from a number of RESTful back end services.
One of the cornerstones of REST is the identification and manipulation of resources through HTTP requests.
For example, if we had a RESTful pizza service that exposed a pizzas
endpoint, then doing an HTTP GET
on this endpoint would return a collection of resources, each of which is the representation of a pizza.
Each pizza would be uniquely identifiable with an attribute such as an id
. This id
can then be used to fetch and manipulate a specific pizza resource.
Making a GET
request to the pizzas/:id
endpoint would return the pizza with the requested id
.
Making a PUT
request to the pizzas/:id
endpoint with a pizza resource in the request body would update the specified resource with the supplied values.
Making a DELETE
request to the endpoint would delete that resource.
Here’s the overall picture:
In an Angular application, we would use the HttpClient
module to make HTTP requests, so if we implemented a matching PizzaService
in an Angular app, it would look something like this:
The HttpClient
's generic methods ensure that strongly typed Pizza
objects are returned to components that use the service.
However, if slightly more complex transformations are required between the representations of the object on the back end, and what’s required to be displayed on the front end, we will need to build our own transformation mechanism.
Let us, for our example, make pizzas have a cookedOn
property, which is returned as a string by the API. We would like to parse it into a moment object, which gives us more control on how it can be displayed in the application.
Also, while posting a pizza back to the API, the cookedOn
field will be ignored and doesn’t need to be posted.
For transformations such as these, I use a mechanism that I call a Serializer
, which converts the raw JSON returned by the API into a strongly typed object with the required properties.
Here is an example PizzaSerializer
:
And the PizzaService
, now enhanced to use the serializer.
Now, what is important to note here, is that, if we had another back end service that returned burgers instead of pizzas, the matching front end service would do much the same as the PizzaService
, only now it performs the same operations on Burger
objects instead of Pizza
objects.
The only differences in implementation would be in the serializer, due to possible differences in the model.
In the process of building out our Angular application, we implemented a large number of services to fetch and manipulate the data from the back end.
So we ended up with a bunch of classes that essentially did the same thing — fetching specific resources from the back end, transforming them into the front end’s representations of them, and updating, creating or deleting them from the back end as required.
This eventually got to the point where there was a lot of code duplication which needed to be addressed in some way.
The solution was to take a generic approach that would work for any resource fetched from a RESTful API, as long as the resource conformed to some basic constraints.
If we assume that everything that comes back from the API is uniquely identifiable, it means that every object we receive has an id
field, for example.
We can formalize this by creating a Resource
model:
export class Resource {
id: number
}
Now, every model that we create for a resource fetched from the back end, such as Burger
or Pizza
, can inherit from Resource
, thereby giving it an id
property.
export class Pizza extends Resource {
//id is inherited from Resource
name: string;
cookedOn: Moment;
}
Another component we will need to genericize is the Serializer
. We will do this by specifying an interface that all serializers will implement.
Now, we use this generic model and interface (and some cool TypeScript generics) to implement a generic service that works for any resource.
A PizzaService
that extends this generic service now won’t need the writing of any CRUD code!
Implementing a new service for burgers is now just a matter of creating the class and getting it to inherit from the generic ResourceService
as above.
There we have it, a generic implementation for fetching and transforming data from a RESTful API!
However, there is yet another scenario we haven’t accounted for.
REST allows resources to have children — i.e. sub-resources which have an association with the main resource object.
To support such sub-resources, we will need to extend our resource model as follows:
export class Resource {
id: number;
parentId?: number;
}
Adding the parentId
field allows us to recognize a sub-resource.
We will, however, need to implement another generic service, a SubResourceService
, which takes an additional parameter to identify the parent endpoint, and will need to be passed in the parentId
to operate on the resource.
Conclusion
Now we have a full-featured, generic implementation for CRUD on any resource or sub-resource provided by a RESTful API.
This ensures that the code that does all the heavy lifting in terms of fetching, transforming and dispatching data is encapsulated in two classes, and can be unit tested in isolation.
The Serializers expose pure functions that can also be thoroughly unit tested to ensure high quality, working software.