I have created new library rest-core
and rest-ngx
. It's looks really like ngx-resource
.
Please try to switch to rest-core
with rest-ngx
module.
Resource (REST) Client for Angular 2
To use the module install the module using below command
npm install ngx-resource --save
Buy me a Coffee with PayPal.Me
Good explanation how to use the library in the article "Angular2, a rest client interface" by Jonathan Serra
Creating simple resource CRUD (./resources/NewsRes.ts)
import {Injectable} from '@angular/core';
import {Resource, ResourceParams, ResourceAction, ResourceMethod} from 'ngx-resource';
import {RequestMethod} from '@angular/http';
interface IQueryInput {
page?: number;
perPage?: number;
dateFrom?: string;
dateTo?: string;
isRead?: string;
}
interface INewsShort {
id: number;
date: string;
title: string;
text: string;
}
interface INews extends INewsShort {
image?: string;
fullText: string;
}
@Injectable()
@ResourceParams({
url: 'https://domain.net/api/users'
})
export class NewsRes extends Resource {
@ResourceAction({
isArray: true
})
query: ResourceMethod<IQueryInput, INewsShort[]>;
@ResourceAction({
path: '/{!id}'
})
get: ResourceMethod<{id: any}, INews>;
@ResourceAction({
path: '/{!id}'
})
get2: ResourceMethodStrict<INews, {id: any}, INews>;
@ResourceAction({
method: RequestMethod.Post
})
save: ResourceMethod<INews, INews>;
@ResourceAction({
method: RequestMethod.Put,
path: '/{!id}'
})
update: ResourceMethod<INews, INews>;
@ResourceAction({
method: RequestMethod.Delete,
path: '/{!id}'
})
remove: ResourceMethod<{id: any}, any>;
// Alias to save
create(data: INews, callback?: (res: INews) => any): INews {
return this.save(data, callback);
}
}
Or it is possible to use predefined CRUD resource which will do exactly the same as resource above
@Injectable()
@ResourceParams({
url: 'https://domain.net/api/users'
})
export class NewRes extends ResourceCRUD<IQueryInput, INewsShort, INews> {}
Using in your app.
First of all need to add Resource Module to your app Module. Simply import ResourceModule.forRoot() to your all root module
@NgModule({
imports: [
BrowserModule,
ResourceModule.forRoot()
],
declarations: [
AppComponent
],
bootstrap: [AppComponent]
})
export class AppModule {
}
Then inject resource into your components
import {Component, OnInit} from '@angular/core';
import {NewsRes} from '../../resources/index';
@Component({
moduleId: module.id,
selector: 'news-component',
templateUrl: 'news.page.component.html',
styleUrls: ['news.page.component.css'],
})
export class PageComponent implements OnInit {
newList: INewsShort[] = [];
constructor(private newsRes:NewsRes) {}
ngOnInit():any {
// That will execute GET request https://domain.net/api/users
// and after will assign the data to this.newsList
this.newList = this.newsRes.query();
// Execute GET request https://domain.net/api/users?page=1&perPage=20
this.newList = this.newsRes.query({page: 1, perPage: 20});
// Execute GET request https://domain.net/api/users/12
// and assing the data to oneNews variable
let oneNews = this.newsRes.get({id: 12});
// or
let otherOneNews: INews = null;
this.newsRes.get({id: 12}, (receivedNews: INews) => {
otherOneNews = receivedNews;
// do some magic after receiving news
});
// or :)
let otherSomeNews = this.newsRes.get({id: 12});
otherSomeNews
.$observable
.subscribe(
(receivedNews: INews) => {
otherOneNews = receivedNews;
// do some magic after receiving news
}
);
// Also you can cancel the requests
let news = this.newsRes.get({id: 12});
news.$abortRequest();
// That kind of ways with callback, $observable and $abortRequest
// can be used on all methods
// Creating the news
let newNews:INews = {
date: '17.06.2016',
title: 'The great day',
text: 'The best day ever',
fullText: 'Should be full text here';
}
// That will execute the POST request to https://domain.net/api/users
// Expected to receive created news object which will be assigned to newNews
let newNews = this.newsRes.save(newNews);
// and so on
}
}
QueryParams Conversion
You can define the way query params are converted Set the global config at the root of your app.
ResourceGlobalConfig.getParamsMappingType = TGetParamsMappingType.<CONVERTION_STRATEGY>
{
a: [{ b:1, c: [2, 3] }]
}
With <CONVERTION_STRATEGY>
being an enumerable within
No convertion at all.
Output: ?a=[Object object]
All array elements will be indexed
Output: ?a[0][b]=10383&a[0][c][0]=2&a[0][c][1]=3
Implements the standard $.params way of converting
Output: ?a[0][b]=10383&a[0][c][]=2&a[0][c][]=3
Added onError
method. See issue #141
Added JQueryParamsBracket
method to convert query params
Added toObservable
flag to ResourceAction or ResourceParam to get observable directly from method
Method type ResourceMethodObservable<I, O>
should be used
Added bodySerializer
method to implement custom data serializer
Added toPromise
flag to ResourceAction or ResourceParam to get promise directly from method
Method type ResourceMethodPromise<I, O>
should be used
Added path prefix param pathPrefix
Added angular v2 support by setting flag angularV2
on ResourceParams
to true
. Fixes #116.
- Breaking changes on Resource class. All methods and properties starts from
$
in order to split users methods and Resource methods (starts from$
):
getUrl
->$getUrl
setUrl
->$setUrl
getPath
->$getPath
setPath
->$setPath
getHeaders
->$getHeaders
setHeaders
->$setHeaders
getParams
->$getParams
setParams
->$setParams
getData
->$getData
setData
->$setData
createModel
->$createModel
requestInterceptor
->$requestInterceptor
responseInterceptor
->$responseInterceptor
initResultObject
->$initResultObject
map
->$map
filter
->$filter
getResourceOptions
->$getResourceOptions
_request
->$request
-
Added new flag
lean
to resource params or action. Will prevent adding$
variables to result. Fixes #110 -
Removed full import of
rxjs/Rx
. Might broke your app, if need some extra operators or something else, import them in your app. Fixes #111 -
Removed deprecated static method
create
fromResourceModel
class
Fixes #108
Added ODATA support.
- (Breaking) Removed Injector from Resource class constructor
- Added
cleanData
method to resource to remove from some predefined by response/create model methods/properties from data
Support Angular 4
ResourceModel is simplified.
New model migration steps:
- Model Class
- Remove model decorator.
- Remove
static resourceClass
. - If you have data
id
different then defaultid
, then overwrite methodprotected isNew(): boolean
.Create
resource method will be used ifisNew()
return'strue
, otherwiseupdate
method will be called. - Static
create
method does not exists anymore. Please usemyResource.createModel()
.
- Model's resource class
- Remove
static model
- Overwrite default
initResultObject()
resource method. Normally it should just containreturn new MyModel()
- Remove
Please check bellow the example.
Added resource method initResultObject
which is used to create return object or items in returned array.
The method should return object. If method $setData
exists on the return object, then it will be called with
received data, so the method is kind of constructor to set received data. If method does not exists on the
object, then Object.assign will be used to set received data. See example below.
map
method is used to create main return object
map
method will be called with null
as data in order to create initial object
and again will be called with real data after receiving.
See example of usage below
Added possibility to switch array/object mapping to get params. For now it's possible to switch between 2 ways of mapping, which are:
TGetParamsMappingType.Plain
(default and old behavior)
params: ['one', 'two']
will be mapped to/some/url/?params=one¶ms=two
TGetParamsMappingType.Braket
(proposed by PR #87)
params: ['one', 'two']
will be mapped to/some/url?params[0]=one¶ms[1]=two
params: { data: ['one', 'two'] }
will be mapped to/some/url?params[data][0]=one¶ms[data][1]=two
Added protected method _request to Resource class. Can be used to replace default http requests with custom one.
@ResourceParams(options: ResourceParamsBase)
The decorator is used to define default resource parameters (can be overwritten with method parameters).
@ResourceParams accepts object ResourceParamsBase
type (description below).
@ResourceAction(options: ResourceActionBase)
Decorates methods. @ResourceAction accepts object ResourceActionBase
type (description below).
All default decorated options will be overwritten for the method.
Resource method type with specified input data type
as I
and output data type
as O
In fact it's a function type (input?: I, callback?: (res: O) => void, onError?: (res: Response) => any): ResourceResult
Resource method type with specified input data type
as I
and output data type
as O
In fact it's a function type (input?: I, callback?: (res: O) => void, onError?: (res: Response) => any): Promise
Resource method type with specified input data type
as I
and output data type
as O
In fact it's a function type (input?: I, callback?: (res: O) => void, onError?: (res: Response) => any): Observable
Resource method type with specified input body data type
as IB
, input path data type
as IP
and output data type
as O
In fact it's a function type (body?: IB, params?: IP, callback?: (res: O) => any, onError?: (res: Response) => any): ResourceResult
ResourceMethodStrict
developed in purpose to respove issue #76
Every request method is returning given data type which is extended by ResourceResult
export type ResourceResult<R extends {}> = R & {
$resolved?: boolean; // true if request has been executed
$observable?: Observable<R>; // Observable for the request
$abortRequest?: () => void; // method to abort pending request
}
export interface ResourceParamsCommon {
url?:string;
pathPrefix?:string;
path?:string;
headers?:any;
params?:any;
data?:any;
removeTrailingSlash?: boolean;
addTimestamp?: boolean | string;
withCredentials?: boolean;
lean?: boolean;
angularV2?: boolean;
toPromise?: boolean;
toObservable?: boolean;
bodySerializer?(body: any): string;
[propName: string]: any;
}
Default resource common address
Default: empty
Ex: https://domain.com/api
Default resource path prefix to api.
url + pathPrefix + path
Default resource path to api.
Can contain path params, which are between { }
.
If path param is with !
prefix, then the param is mandatory
If path param is with :
prefix, then the param will be removed from post data
Default: empty
Ex: /users/{id}
Ex2: /users/{!id}
Ex3: /users/{:id}
Ex4: /users/{!:id}
Default resource HTTP headers.
It should be object where key is header name and value is header value
Default:
{
'Accept': 'application/json',
'Content-Type': 'application/json'
}
Default resource path/get params
Default: null
Ex: {"mode": "user", "id": "@_id", "_id": 0}
Default resource body params
The params will be added to data object if they does not exists
Default: null
Ex: {"mode": "user", "isActive": true}
Remove trailing slashed from url
Default: true
Will add timestamp to the url
Can be boolean or string representation of parameter name
Default: false
Will add withCredentials option to request options
Default: false
Use the flag for angular version 2
To return promise directly from resource method
To return observable directly from resource method. The observable will be lazy
by default if isLazy
is not specified.
Custom method to serialise data body
export interface ResourceParamsBase extends ResourceParamsCommon {
add2Provides?: boolean;
providersSubSet?: string;
}
To create service provider and it to ResourceModule.forRoot()
Default: true
To create service provider and it to ResourceModule.forChild()
Default: null (so it goes to forRoot())
export interface ResourceActionBase extends ResourceParamsCommon {
method?:RequestMethod; // from angular `@angular/http`
isArray?: boolean;
isLazy?: boolean;
requestInterceptor?: ResourceRequestInterceptor;
responseInterceptor?: ResourceResponseInterceptor;
initResultObject?: ResourceResponseInitResult;
map?: ResourceResponseMap;
filter?: ResourceResponseFilter;
rootNode?: string;
skipDataCleaning?: boolean;
}
All parameters will overwrite default one from ResourceParamsBase
Http request method of the action.
Ex: method: RequestMethod.Get
Default: method: RequestMethod.Get
Used if received data is an array
Is isLazy
set to true, then the request will not be executed immediately. To execute the request you should subscribe to observable and handle responses by yourself.
(req: Request): Request;
Custom request modifier for the method
Default request interceptor is a function which recieves Request
object from anglar2/http
Default: doing nothing
(observable:Observable<any>, request?:Request, methodOptions?: ResourceActionBase):Observable<any>;
Custom response interceptor for the method
Default response interceptor is a function which receives Observable
object from rxjs/Observable
and returns also Observable
object.
Default:
function (observable:Observable<any>):Observable<any> {
return observable.map(res => res._body ? res.json() : null);
}
(): any;
Custom object creator. Added on Ver 1.14.0
(item: any):any;
Custom response data mapper.
Will be called for each array element if response is an array.
Will called for the object if response is an object
Called before mapping data
(item: any):boolean;
Custom response filter.
Will be called for each array element if response is an array.
Will called for the object if response is an object
Called before map method
The data sent to the API will be wrapped into the root node provided
Every time before making the request the data object is cleaned from ResourceModel
system variables which are staring
with $
prefix or toJSON function will be called if it exists on data object.
By setting the flag to true
the object will not be cleaned from system variables.
Note: For all non GET request all data object will be send in the request body as json. In case of GET requset the data object will be send as query parameters. Parameters, which are has been used for path params, will be removed from query list (only for GET request).
To get url. Used in methods.
To set resource url
To get path. Used in methods
To set resource path
To get headers. Used in methods.
To set resource headers
To get params. Used in methods.
To set resource params
To get data. Used in methods.
To set resource data
To serialize the data before send. Default JSON.stringify
Default request interceptor
$responseInterceptor(observable: Observable<any>, req: Request, methodOptions?: ResourceActionBase): Observable<any>
Default response interceptor
Called by method if needs to trim trailing slashes from final url
Called on return object initialization
Default response mapper
Default filter method. By default always true
Default object cleaning.
Returns clean from functions and ($resolved
, $observable
, $abortRequest
, $resource
) variables
The class is extends with Resource and has predefined 5 methods:
- get(data, callback) to execute GET request;
- query(data, callback) to execute GET and recieve an array
- save(data, callback) to execute POST request;
- update(data, callback) to execute PUT request;
- remove(data, callback) or delete(data, callback) to execute DELETE request.
Static class to define global common params for all Resources globally
Defines url
Defines path
Defines headers
Defines params
Defines global default add2Providers
flag
Defines global default lean
flag
Defines global default toPromise
flag
Defines global default toObservable
flag
Defines data
Defines mapping method of arrays/objects to get params
Lower number - higher priority
- Defined by @ResourceAction decorator
- Sett by setUrl method of the resource
- Defined by @ResourceParams decorator
- Defined in ResourceGlobalConfig
- Default value
OData (Open Data Protocol) is an OASIS standard that defines a set of best practices for building and consuming RESTful APIs.
This module also includes a class for dealing with ODATA endpoints.
import {ResourceODATA, ResourceODATAParams} from 'ngx-resource';
@ResourceODATAParams({entity: News, name: "News"})
export class NewsResource extends ResourceODATA<News> {
}
Then when using this resource you already can use the following predefined methods:
- newsResource.get({id: 1})
- newsResource.save(news)
- newsResource.search({"$filter": "ODATA filter expression", "$search": "search string", "$expand": "comma separated list of fields to include in response", "$limit": "count limit for results"})
For more information please check out the standard.
More capabilities will be added in the future.
import {Request, Response} from '@angular/http';
import {Observable, Subscriber} from 'rxjs';
import {AuthServiceHelper} from '../helpers/index';
import {Resource} from 'ngx-resource';
export class AuthGuardResource extends Resource {
private deferredQ: Subscriber<any>[] = [];
private configListenerSet: boolean = false;
$getHeaders(methodOptions: any): any {
let headers = super.$getHeaders();
// Extending our headers with Authorization
if (!methodOptions.noAuth) {
headers = AuthServiceHelper.extendHeaders(headers);
}
return headers;
}
$responseInterceptor(observable: Observable<any>, request: Request, methodOptions: ResourceActionBase): Observable<any> {
return Observable.create((subscriber: Subscriber<any>) => {
observable.subscribe(
(res: Response) => {
if (res.headers) {
let newToken: string = res.headers.get('X-AUTH-TOKEN');
if (newToken) {
AuthServiceHelper.token = newToken;
}
}
subscriber.next((<any>res)._body ? res.json() : null);
},
(error: Response) => {
if (error.status === 401) {
AuthServiceHelper.token = null;
}
//console.warn('BaseResource request error', error, request);
subscriber.error(error);
},
() => subscriber.complete()
);
});
}
}
import { Injectable } from '@angular/core';
import { RequestMethod } from '@angular/http';
import { AppProject } from '../../project/app.project';
import { ResourceAction, ResourceMethod, ResourceParams } from 'ngx-resource';
import { AuthGuardResource } from './authGuard.resource';
@Injectable()
@ResourceParams({
url: AppProject.BASE_URL + '/auth/v1'
})
export class AuthResource extends AuthGuardResource {
@ResourceAction({
method: RequestMethod.Post,
path: '/login',
// Custom param
noAuth: true
})
login: ResourceMethod<{login: string, password: string}, any>;
@ResourceAction({
method: RequestMethod.Get,
path: '/logout'
})
logout: ResourceMethod<void, any>;
}
export interface ITestModel {
id?: string;
name?: string;
}
export interface ITestQueryInput {
name?: string;
}
export class TestModel extends ResourceModel<TestResource> implements ITestModel {
id: string;
name: string;
$setData(data: any) {
// You can overwrite $setData method
if (data) {
this.id = data.id;
this.name = data.name;
// do something else
}
}
protected isNew(): boolean {
return !this.id;
}
}
@Injectable()
@ResourceParams({
url: 'https://domain.net/api/test'
})
export class TestResource extends ResourceCRUD<ITestQueryInput, TestModel, TestModel> {
$initResultObject(): TestModel {
return new TestModel();
}
}
import {Component, OnInit} from '@angular/core';
@Component({
moduleId: module.id,
selector: 'test-component',
templateUrl: 'test.page.component.html',
styleUrls: ['test.page.component.css'],
})
export class TestComponent implements OnInit {
constructor(private testRes: TestResource) {}
ngOnInit() {
let modelTest = this.testRes.createModel();
console.log('New modelTest', modelTest);
modelTest.$save().$observable.subscribe(() => {
console.log('Saved and updated modelTest', modelTest);
});
let modelTest2 = this.testRes.query();
console.log('Array of models', modelTest2);
modelTest2.$observable.subscribe(() => {
// Data received
console.log('Array filled with test models', modelTest2);
let modelTest3 = modelTest2[1];
modelTest3.name = 'Roma';
modelTest3.$save().$observable.subscribe(() => {
console.log('Saved and updated', modelTest3);
});
});
}
);
export class CTest {
prop1: string = '';
prop2: string = '';
get prop(): string {
return this.prop1 + ' ' + this.prop2;
}
constructor(data: any = null) {
this.$setData(data);
}
$setData(data: any) {
if (data) {
this.prop1 = data.prop1;
this.prop2 = data.prop2;
// do something else
}
}
}
@Injectable()
@ResourceParams({
url: 'https://domain.net/api/test'
})
export class TestRes extends Resource {
@ResourceAction({
isArray: true
})
query: ResourceMethod<any, CTest[]>;
@ResourceAction({
path: '/{!id}'
})
get: ResourceMethod<{id: any}, CTest>;
$initResultObject(): any {
return new CTest();
}
}
@Component({
moduleId: module.id,
selector: 'test-component',
templateUrl: 'test.page.component.html',
styleUrls: ['test.page.component.css'],
})
export class TestComponent implements OnInit {
list: CTest[] = [];
test: CTest;
prop: string;
constructor(private testRes:TestRes) {}
ngOnInit():any {
this.list = this.testRes.query();
this.test = this.testRes.get({id:1});
this.prepareData(); // will not set prop, test is not yet resolved
console.log(this.test.prop); // a space ' ' will be returned because data is not yet received
// so to get the prop we will need to wait data to be received
this.test
.$observable
.subscribe(
// Now the data is received and assigned on the object
() => this.prepareData()
);
}
private preprareData() {
if (this.test && this.test.$resolved) {
this.prop = this.test.prop;
}
}
}