The typical way of consuming a REST API in Angular is to write custom services using the HttpClient
. The code of such a service can get pretty hard to maintain as it can be seen in the tour of heroes tutorial. This also increases the risk of inconsistency in the code because as every developer in the team starts to develop his own methods for standard HTTP calls.
A solution for removing the risk of collecting lots of boilerplate, unmentionable and inconsistent code is to use the ngx-resource-factory. This library is inspired by $resource which is available for AngularJS.
The library as well as the snippets in this article are compatible with Angular 5, 6, 6.1, 7 and 7.1.
Installing the library
The package is available on npm, so you can go ahead and install it by using npm or yarn:
npm install ngx-resource-factory --save
yarn add ngx-resource-factory
Add ResourceRegistry and HttpClientModule
All resources will be provided in the ResourceRegistry
. This service must be provided in your root module or in the module where you plan to use the resources. The library also makes use of the HttpClientModule
, so make sure to also add to the imports.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { ResourceRegistry } from 'ngx-resource-factory/resource/resource-registry';
import { AppComponent } from './app.component';
import { TodoComponent } from './todo.component';
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
],
declarations: [
AppComponent,
TodoComponent
],
bootstrap: [
AppComponent
],
providers: [
ResourceRegistry
]
})
export class AppModule { }
Declaring a simple resource
The implementation makes use of the free test api provided by typicode. There are several endpoints available, this example is using the todo endpoint
import { Injectable } from "@angular/core";
import { Resource } from 'ngx-resource-factory/resource/resource';
import { ResourceConfiguration } from 'ngx-resource-factory/resource/resource-configuration';
import { ResourceInstance } from 'ngx-resource-factory/resource/resource-instance';
export class Todo extends ResourceInstance {
userId: number;
id: number;
title: string;
completed: boolean;
}
@Injectable({
providedIn: 'root'
})
@ResourceConfiguration({
name: 'TodoResource',
url: 'https://jsonplaceholder.typicode.com/todos/:pk/',
pkAttr: 'id',
instanceClass: Todo,
stripTrailingSlashes: false,
})
export class TodoResource extends Resource<Todo> {
}
Note: The providedIn: 'root'
in the @Injectable
decorator is available since Angular 6. Make sure to provide the service under provider in your module for older versions.
By convention you have to declare :pk
as a placeholder for the insertion of the primary key. This will be used for get (GET), update (PUT or PATCH) or remove (DELETE) calls. The pk will be only added if it is provided in the query params or in the payload, based on the configured pkAttr
. List (GET) and save (POST) will use the url without :pk
.
Make sure you have a valid configuration for “stripTrailingSlashes” base on your API. In our test the API accepts both. An API build with Django Rest Framework will by default add a slash and cause a redirect if stripTrailingSlashes is set to true and the request data will be lost.
Query data
After the resource is defined and provided it can be added into other services or components. Bellow is a simple component that fetches the list of todos using the TodoResource
.
import { Component, Input, OnInit } from '@angular/core';
import { TodoResource, Todo } from './todo.resource';
@Component({
selector: 'app-todo',
template: `<h1>Todo List!</h1><pre>{{ todoList | json }}</pre>`,
})
export class TodoComponent implements OnInit {
todoList: Todo[] = []
constructor(private todoResource: TodoResource) {
}
ngOnInit() {
this.todoResource.query({}).$promise
.then((data) => {
this.todoList = data;
})
.catch((error) => {
console.log(error);
});
}
}
Example app: https://stackblitz.com/edit/ngx-resource-factory-simple
Custom props
There are two ways you can use to register custom properties on resource object.
- Define custom properties on load. The load method will be executed after each read method (save / get) .
export class Todo extends ResourceInstance {
userId: number;
id: number;
title: string;
completed: boolean;
$date: number;
load() {
this.$date = new Date().getTime();
return this;
}
}
- Define custom getters. They will be executed only when called on a resource object.
export class Todo extends ResourceInstance {
userId: number;
id: number;
title: string;
completed: boolean;
get $lazyDate() {
return new Date().getTime();
}
}
Note: By prefixing a property with $
you make sure that this property is not being sent to the REST API on a save or update call.
Example app: https://stackblitz.com/edit/ngx-resource-factory-custom-props
Resource modifiers
There are also use cases where you want to modify data in a certain way after reading it from the database. This can be also done within the load method:
export class Todo extends ResourceInstance {
userId: number;
id: number;
title: string;
completed: boolean;
load() {
this.title = 'PREFIX: ' + this.title;
return this;
}
}
On the other hand the dump()
method can be used if the data must be modified before it is send to the REST API.
export class Todo extends ResourceInstance {
userId: number;
id: number;
title: string;
completed: boolean;
date: string;
dump() {
this.date = new Date().getTime();
return this;
}
}
Implementation of a complex resource
For this we try to use an API that is a bit more complex: https://reqres.in/api/users
Make sure to have stripTrailingSlashes: true,
as this API is not using trailing slashes, and due to a redirect it will not behave as expected.
The resource default configuration expects to receive a list, but this time it will receive an object. This is where the dataAttr
comes handy. Also you must define on which type of request you want to use the data attribute.
dataAttr: 'data',
useDataAttrForList: true,
useDataAttrForObject: false,
Since the application returns an access-control-allow-origin: *
we have to make sure the withCredentials
is set to false: https://stackoverflow.com/a/45723981/5382179
withCredentials: false,
This is how the resource configuration should look at the end:
import { Injectable } from "@angular/core";
import { Resource } from 'ngx-resource-factory/resource/resource';
import { ResourceConfiguration } from 'ngx-resource-factory/resource/resource-configuration';
import { ResourceInstance } from 'ngx-resource-factory/resource/resource-instance';
export class User extends ResourceInstance {
id: number;
first_name: string;
last_name: string;
avatar: string;
}
@Injectable({
providedIn: 'root'
})
@ResourceConfiguration({
name: 'UserResource',
url: 'https://reqres.in/api/users/:pk/',
pkAttr: 'id',
instanceClass: User,
stripTrailingSlashes: true,
dataAttr: 'data',
useDataAttrForList: true,
useDataAttrForObject: false,
withCredentials: false,
})
export class UserResource extends Resource<User> {
}
Example app: https://stackblitz.com/edit/ngx-resource-factory-comple
Overwrite resource action
Under certain conditions you want to replace the behavior of the implemented resource actions. The API used to get the users in the previous example is such a case. It uses a data attribute for get calls on list and object but for create and update it uses just the object. To achieve this we must define the dataAttr
on the get method:
@ResourceConfiguration({
name: 'UserResource',
url: 'https://reqres.in/api/users/:pk/',
pkAttr: 'id',
instanceClass: User,
stripTrailingSlashes: true,
dataAttr: 'data',
useDataAttrForList: true,
useDataAttrForObject: false,
withCredentials: false,
})
export class UserResource extends Resource<User> {
@ResourceAction({
method: ResourceActionHttpMethod.GET,
isList: false,
dataAttr: 'data',
})
get: ResourceActionMethod<any, any, User>;
}
An example app that makes use of this can be found here: https://stackblitz.com/edit/ngx-resource-factory-crud (this example also has all crud methods implemented)
This approach can also be used to create custom resource actions.
Conclusion
The ngx-resource-factory
provides an elegant solution to interact with REST APIs in Angular applications. The library is released under the MIT License and is under active development. You are warmly welcome to contribute or post any issues on the repository page: https://github.com/beachmachine/ngx-resource-factory