ngx-resource-factory is an Angular library that enables you to work with RESTful APIs in an easy way. The main features include a resource store for holding changes on resource instances and related resource instances to commit them at once, and an advanced resource caching.
Note: This library is for Angular 8.x and above!
This library is currently in an early stage of development and is missing lots of features as well as a proper documentation.
You can find a tutorial on using this library at this blog entry by @nezhar
To use ngx-resource-factory in your project you have to install it via npm:
npm i ngx-resource-factory --save
The next step is defining a resource. Typically you create a services folder which will hold a ServicesModule
in
which the resources will be declared. Resources should also have their own directory. E.g. the UserResource
would
be located in app/services/resources/user.resource.ts
import { Injectable } from "@angular/core";
import { environment } from '../../../environments/environment';
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 {
pk: number;
url: string;
username: string;
email: string;
}
@Injectable()
@ResourceConfiguration({
name: 'UserResource',
url: environment.apiUrl + 'user/:pk/',
pkAttr: 'pk',
instanceClass: User,
stripTrailingSlashes: false,
})
export class UserResource extends Resource<User> {
}
After defining the first resource service it is required to declare it in a module so that we can inject it into
components. Here, in app/services/services.module.ts
, we are going to define the ServicesModule
, which will
load the UserResource
.
import { ModuleWithProviders, NgModule } from '@angular/core';
import { UserResource } from './resources/user.resource';
@NgModule({
imports: [],
exports: [],
declarations: [],
providers: [/* declare in `forRoot()` */],
})
export class ServicesModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: ServicesModule,
providers: [
UserResource
]
}
}
}
Finally we have to add the NgxResourceFactoryModule and the ServicesModule to the main Angular module.
import { NgxResourceFactoryModule } from 'ngx-resource-factory';
import { ServicesModule } from './services/services.module';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// ...
NgxResourceFactoryModule.forRoot(),
ServiceModule.forRoot(),
// ...
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
You can now inject the resource into your component and start using it.
import { Component, OnInit } from '@angular/core';
import { ResourceModel } from 'ngx-resource-factory/resource/resource-model';
import { UserResource, User } from './services/resources/user.resource';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'app';
users: ResourceModel<User>[] = [];
constructor(private userResource: UserResource) {
}
ngOnInit() {
const queryParam = {};
this.userResource.query(queryParam).$promise
.then((data) => {
this.users = data;
console.log(data);
})
.catch((error) => {
console.log(error);
});
}
}
ngx-resource-factory exposes the REST APIs methods via its ActionMethod
methods. The default defines the typical
RESTful methods on the resource class as follows:
.query()
executesGET
for getting lists.get()
executesGET
for getting instances.save()
executesPOST
for creating instances.update()
executesPATCH
for updating instances.remove()
executesDELETE
for removing instances
Each of this resource methods can take the parameters, in the given order, as follows:
query
optional parameter for URL query data as objectpayload
optional parameter for the requests payload as objectsuccessCb
optional parameter for the success callback as functionerrorCb
optional parameter for the error callback as function
Each of this resource methods is also available on a resource instance, prefixed with a $
:
.$query()
executesGET
for getting lists.$get()
executesGET
for getting instances.$save()
executesPOST
for creating instances.$update()
executesPATCH
for updating instances.$remove()
executesDELETE
for removing instances
This resource instance methods can take the parameters, in the given order, as follows:
query
optional parameter for URL query data as objectsuccessCb
optional parameter for the success callback as functionerrorCb
optional parameter for the error callback as function
For a better understanding how to define custom action methods, have a look at the Resource<T>
type that defines
the default methods.
import { Injectable } from "@angular/core";
import { ResourceBase } from "ngx-resource-factory/resource/resource";
import { ResourceInstance } from "ngx-resource-factory/resource/resource-instance";
import { ResourceAction } from "ngx-resource-factory/resource/resource-action";
import { ResourceActionMethod } from "ngx-resource-factory/resource/resource-action-method";
import { ResourceActionHttpMethod } from "ngx-resource-factory/resource/resource-action-http-method";
@Injectable()
export abstract class Resource<T extends ResourceInstance> extends ResourceBase {
@ResourceAction({
method: ResourceActionHttpMethod.GET,
paramDefaults: [],
isList: true,
})
query: ResourceActionMethod<any, any, T[]>;
@ResourceAction({
method: ResourceActionHttpMethod.GET,
isList: false,
})
get: ResourceActionMethod<any, any, T>;
@ResourceAction({
method: ResourceActionHttpMethod.POST,
paramDefaults: [],
isList: false,
invalidateCache: true,
})
save: ResourceActionMethod<any, any, T>;
@ResourceAction({
method: ResourceActionHttpMethod.PATCH,
isList: false,
invalidateCache: true,
})
update: ResourceActionMethod<any, any, T>;
@ResourceAction({
method: ResourceActionHttpMethod.DELETE,
isList: false,
invalidateCache: true,
})
remove: ResourceActionMethod<any, any, T>;
}
Your resource class may inherit from Resource<T>
or from ResourceBase
, if you do not want to
have the default action methods. Define custom action methods on your resource class as shown above.
@Injectable()
@ResourceConfiguration({
name: 'UserResource',
url: environment.apiUrl + 'user/:pk/',
pkAttr: 'pk',
instanceClass: User,
stripTrailingSlashes: false,
})
export class UserResource extends Resource<User> {
@ResourceAction({
method: ResourceActionHttpMethod.POST,
isList: false,
invalidateCache: true,
urlSuffix: 'deactive/'
})
deactivate: ResourceActionMethod<any, any, T>;
}
It is possible to add multiple params in the URL.
@Injectable()
@ResourceConfiguration({
name: 'UserResource',
url: environment.apiUrl + 'user/:pk/',
pkAttr: 'pk',
instanceClass: User,
stripTrailingSlashes: false,
paramDefaults: [
new ResourceParamDefaultFromPayload('key1', 'key1'),
new ResourceParamDefaultFromPayload('key2', 'key2'),
],
})
export class UserResource extends Resource<User> {
}
constructor(private userResource: UserResource) {
userResource.getOptions().paramDefaults.push(new ResourceParamDefault('key1', 'key1'));
}
For custom action methods:
@ResourceAction({
method: ResourceActionHttpMethod.POST,
paramDefaults: [
new ResourceParamDefaultFromPayload('key1', 'key1'),
new ResourceParamDefaultFromPayload('key2', 'key2'),
],
isList: false,
});
Action methods give you three ways to handle responses. You can use the stub object returned
by an action method, that gets filled with data as soon as the response was received, you can use
the .$promise
property to handle the response with a Promise
, or you can use the .$observable
property to handle the response with an Observable
.
This may look as follows:
import { Component, OnInit } from '@angular/core';
import { ResourceModel } from 'ngx-resource-factory/resource/resource-model';
import { UserResource, User } from './services/resources/user.resource';
@Component({
selector: 'app-my',
templateUrl: './my.component.html',
styleUrls: ['./my.component.scss']
})
export class MyComponent implements OnInit {
users: ResourceModel<User>[] = [];
constructor(private userResource: UserResource) {
}
ngOnInit() {
// Stub
this.users = this.userResource.query();
// Promise
this.userResource.query().$promise
.then((data) => {
this.users = data;
});
// Observable
this.userResource.query().$observable
.subscribe((data) => {
this.users = data;
});
}
}
Angular 6.x is using RxJS 6.x. This has brings some changes in the API of RxJS and requires to additionally install rxjs-compat. Make sure the version of rxjs
and rxjs-compat
are compatible.
This can be removed and rxjs-compat
can be moved to peer dependencies once the support for Angular 5.x will be dropped.
The package makes use of the URL API, which is not provided in IE11 or earlier: https://caniuse.com/#feat=url
Make sure to install the URL API polyfill (npm install url-polyfill --save
) and add it into polyfills.ts:
import 'url-polyfill';
A file upload service can be easily realized by adding a custom action for the POST-method, here's an example-implementation.
Add a ResourceAction for POST and an 'upload'-ResourceActionMethod to your File-Service (Which we will then use in the component further down):
@app/services/resource/file.resource.ts:
import { environment } from '../../../environments/environment';
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';
import { ResourceAction } from 'ngx-resource-factory/resource/resource-action';
import { ResourceActionMethod } from 'ngx-resource-factory/resource/resource-action-method';
import { ResourceActionHttpMethod } from 'ngx-resource-factory/resource/resource-action-http-method';
export class UploadableFile extends ResourceInstance {
pk: number;
parent_pk: number;
}
@Injectable()
@ResourceConfiguration({
name: 'FileResource',
url: `${environment.api_url}/file/:pk/`,
pkAttr: 'pk',
instanceClass: UploadableFile,
stripTrailingSlashes: false,
dataAttr: 'results',
useDataAttrForList: true,
useDataAttrForObject: false,
totalAttr: 'count',
})
export class FileResource extends Resource<UploadableFile> {
@ResourceAction({
method: ResourceActionHttpMethod.POST,
paramDefaults: [],
isList: false,
invalidateCache: true,
})
upload: ResourceActionMethod<any, any, any>;
}
The content-type will be automatically set by the browser.
In your component's upload-function, set up the payload with FormData() and append the file to it, which we will then POST to the backend with the 'upload'-ResourceActionMethod we have implemented in the service above:
@app/screens/fileupload/fileupload.component.ts:
[..]
import { FileResource } from '@app/services/resource/file.resource';
export class FileUploadComponent extends ModalBaseComponent implements OnInit, OnDestroy {
file: File;
[..]
constructor(
public fileResource: FileResource
)
[..]
onFileChange(event) {
if ( event.target.files.length > 0 ) {
this.file = event.target.files[0]
}
}
uploadAppBinary() {
const payload: FormData = new FormData();
payload.append('path', this.file, this.file.name);
payload.append('parent_pk', this.parent.pk);
this.fileResource.upload({}, payload).$promise
.then((data) => {
console.log('Fileupload success:');
console.log(data);
})
.catch((reason) => {
console.log('Fileupload error:');
console.log(reason);
});
}