⚠ If you use old View Engine compilation or Angular 6.x.x - 11.x.x you need to use 2.x.x lib version.
See more about it here.
- Versions that work with old
View Enginecompilation [2.0.0-2.x.x]. - Versions that work with new
Ivycompilation [3.0.0-x.x.x] and supports Angular from 12.x.x - 21.x.x.
New releases will respect with Angular versions. For example current Angular has 21 version, then lib version will be 21.x.x. The lib will be respect only with major version. Minor and patched version will be own not respect to Angular version. New lib versioning policy helps you choose the right release, depends on your Angular version.
This client can be used to develop Angular 12+ applications working with RESTfull server API.
By RESTfull API means when the server application implements all the layers of the Richardson Maturity Model
and the server provides HAL/JSON response type.
This client compatible with Java server-side applications based on Spring HATEOAS or Spring Data REST.
This client is a continuation of the @lagoshny/ngx-hal-client. You can find out about the motivation to create a new client here. To migrate from
@lagoshny/ngx-hal-clientto this client you can use the migration guide.
You can found examples of usage this client with task-manager-front application that uses server-side task-manager-back application.
- Decorators
- BaseResource
- Resource
- EmbeddedResource
- ResourceCollection
- PagedResourceCollection
- Subtypes support
- Resource projection support
- GetResource
- GetCollection
- GetPage
- CreateResource
- UpdateResource
- UpdateResourceById
- PatchResource
- PatchResourceById
- DeleteResource
- DeleteResourceById
- SearchResource
- SearchCollection
- SearchPage
- CustomQuery
- CustomSearchQuery
Learn about the latest improvements.
To migrate to standalone system instead NgModule use need to change lib configuration.
Before with NgModule system you configured library as:
import { NgxHateoasClientModule } from '@lagoshny/ngx-hateoas-client';
...
@NgModule({
...
imports: [
HttpClientModule,
...
NgxHateoasClientModule.forRoot()
...
]
...
})
export class AppModule {
constructor(hateoasConfig: NgxHateoasClientConfigurationService) {
hateoasConfig.configure({
http: {
rootUrl: 'http://localhost:8080/api/v1'
}
});
}
}
---
NgModulelib configuration method will be removed in the next lib releases, please migrate your code to new standalone configuration No matter your project usedNgModulesystem or standalone you can configure standalone lib in both types
Now with standalone system:
import { provideNgxHateoasClient } from '@lagoshny/ngx-hateoas-client';
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
provideNgxHateoasClient({
http: {
rootUrl: 'http://localhost:8080/api/v1'
}
}),
// ...
],
}Note that you don't need anymore inject manually NgxHateoasClientConfigurationService and configure it.
Now you simple use provideNgxHateoasClient and pass all configuration there.
If you use old NgModule system then you can use standalone lib version like this:
import { provideNgxHateoasClient } from '@lagoshny/ngx-hateoas-client';
@NgModule({
...
imports: [
HttpClientModule,
],
providers: [
...
provideNgxHateoasClient(
{
http: {
rootUrl: 'http://localhost:8080/api/v1'
}
}
),
...
]
...
})
export class AppModule {
// removed constructor with NgxHateoasClientConfigurationService
}To install the latest version use command:
npm i @lagoshny/ngx-hateoas-client@latest --save
To configure lib you need to import provideNgxHateoasClient provider and setup lib configuration.
Minimal configuration looks like this:
import { provideNgxHateoasClient } from '@lagoshny/ngx-hateoas-client';
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
provideNgxHateoasClient({
http: {
rootUrl: 'http://localhost:8080/api/v1'
}
}),
// ...
],
}If you use NgModule system and want configure lib use the next approach:
import { provideNgxHateoasClient } from '@lagoshny/ngx-hateoas-client';
@NgModule({
...
imports: [
HttpClientModule,
],
providers: [
provideNgxHateoasClient({
http: {
rootUrl: 'http://localhost:8080/api/v1'
}
}),
...
]
...
})
export class AppModule {
...
}import { provideNgxHateoasClient } from '@lagoshny/ngx-hateoas-client';
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
provideNgxHateoasClient({
// All Resources will use this url as base url
http: {
rootUrl: 'http://localhost:8080/api/v1'
}
}),
// ...
],
}import { provideNgxHateoasClient } from '@lagoshny/ngx-hateoas-client';
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
provideNgxHateoasClient({
http: {
// Use this router name for default Resources route
defaultRoute: {
rootUrl: 'http://localhost:8080/api/v1'
},
anotherRoute: {
rootUrl: 'http://localhost:9090/api/v1'
}
}
}),
// ...
],
}defaultRoute - it is special router name that all Resources used by default.
anotherRoute - additional Resource route that can be used in Resource @HateoasResource#options to specify it.
See more about other configuration params here.
To represent model class as a resource model extend model class by Resource class.
Besides you need to decorate the class with @HateoasResource decorator that will register your resource class with passed resourceName in hateoas-client.
Suppose you have some Product model class:
export class Product {
public name: string;
public cost: number;
}After making it as a Resource it will look like this:
import { Resource, HateoasResource } from '@lagoshny/ngx-hateoas-client';
/*
Resource name 'products' should be map to server-side resource name that used to build resource self URL.
For example in this case it can be: http://localhost:8080/api/v1/products
*/
@HateoasResource('products')
export class Product extend Resource {
public name: string;
public cost: number;
}Thereafter, the Product class will have Resource methods to work with the product's relations through resource links.
You can create a resource projection class that will map to a server-side projection model. How to do it read in this section.
Also, you can extend model classes with the
EmbeddedResourceclass and decorate with @HateasEmbeddedResource decorator when the model class used as an embeddable entity. You can read more aboutEmbeddedResourcehere.
It is recommended also to declare the Product resource class (others Resources and EmbeddedResources too) in the hateoas-client configuration:
...
provideNgxHateoasClient(
{
// ...
useTypes: {
resources: [Product]
}
}
),
...See more about
useTypesin the configuration section here.
Now you have created a resource class and ready to perform requests for this resource. For this you can use universal on resource built-in HateoasResourceService or a create custom resource service for concrete resource class.
The library has built-in universal on resource HateoasResourceService.
It is a simple service with methods to get/create/update/delete/search resources.
To use it inject HateoasResourceService to a component or a service class after that you can perform resource requests by passing the resource type.
import { Resource, HateoasResource, HateoasResourceService } from '@lagoshny/ngx-hateoas-client';
@HateoasResource('products')
export class Product extends Resource {
...
}
@Component({
...
})
export class SomeComponent {
constructor(private resourceService: HateoasResourceService) {
}
onSomeAction() {
const product = new Product();
product.cost = 100;
product.name = 'Fruit';
this.resourceService.createResource(Product, product)
.subscribe((createdResource: Product) => {
// TODO something
});
};
}Each HateoasResourceService method has the first param is the resource type that extends Resource class and decorated with @HateoasResource.
The resource type uses to build a URL for resource requests and create resources with a concrete class when parsing the server's answer.
More about HateoasResourceService methods see here.
HateoasResourceServiceis a universal service that can work with severalResourcesat a time, you need only pass a desired resource type as the first param. If you have not extra logic to work with your resources then you can inject onceHateoasResourceServiceand use it to work with your resources.
When you have some logic that should be preparing resource before a request you need to create a custom resource service extends HateoasResourceOperation to see more about this here.
To create a custom resource service create a new service and extends it with HateoasResourceOperation and pass resourceType to the parent constructor.
import { Resource, HateoasResource, HateoasResourceOperation } from '@lagoshny/ngx-hateoas-client';
@HateoasResource('products')
export class Product extends Resource {
...
}
@Injectable({providedIn: 'root'})
export class ProductService extends HateoasResourceOperation<Product> {
constructor() {
super(Product);
}
}HateoasResourceOperation has the same methods as HateoasResourceService without resourceType as the first param (because you pass resourceType with service constructor).
To test your services, that are using HateoasResourceOperation or HateoasResourceService you need import NgxHateoasClientModule.forRoot() in the test module.
After that you can inject or mock the next services (if you need it):
NgxHateoasClientConfigurationServiceHateoasResourceService
Below you can find simple lib test examples:
Suppose you have the User and some UserService like that:
import {
HateoasResourceOperation,
HateoasResourceService,
} from '@lagoshny/ngx-hateoas-client';
export class User {
name: string;
age: number;
}
@Injectable()
export class UserService extends HateoasResourceOperation<User> {
constructor(public resourceService: HateoasResourceService) {
super(User);
}
public create(user: User): Observable<Observable<never> | User> {
return super.createResource({body: user});
}
public getAllUsersByAge(age: number): Observable<PagedResourceCollection<User>> {
return this.resourceService.searchPage(User, 'allByAge', {
params: {
age
}
}
);
}
}Note, UserService extends HateoasResourceOperation and uses HateoasResourceService to perform requests.
If you have the app with standalone components then you don't need any lib configuration to test it. You don't need to configure TestBed additionally.
If you prefer to use standard TestBed for testing, you can do that in the following way:
import {
HateoasResourceService,
NgxHateoasClientModule,
PagedResourceCollection,
ResourceCollection,
provideNgxHateoasClientTesting
} from '@lagoshny/ngx-hateoas-client';
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
describe('UserServiceTest', () => {
let hateoasResourceServiceSpy: Mocked<Pick<HateoasResourceService, 'createResource' | 'searchPage'>>;
beforeEach(() => {
hateoasResourceServiceSpy = {
createResource: vi.fn(),
searchPage: vi.fn(),
};
TestBed.configureTestingModule({
providers: [
UserService,
provideNgxHateoasClientTesting(), // also you can provide custom configuration if need it
{provide: HateoasResourceService, useValue: hateoasResourceServiceSpy}
]
});
});
it('should init service', () => {
const userService = TestBed.inject(UserService);
expect(userService).toBeTruthy();
});
it('should create new user', () => {
const userService = TestBed.inject(UserService);
const newUser = new User();
newUser.id = '1';
hateoasResourceServiceSpy.createResource.mockReturnValue(of(newUser));
const user = new User();
user.name = 'Test user';
userService.create(user).subscribe((createdUser: User) => {
expect(createdUser).toBeTruthy();
expect(createdUser.id).toEqual('1');
});
});
it('should return paged user list', () => {
const userService = TestBed.inject(UserService);
const returnedUser = new User();
returnedUser.id = '1';
const resourceCollection = new ResourceCollection<User>();
resourceCollection.resources = [returnedUser];
hateoasResourceServiceSpy.searchPage.mockReturnValue(of(new PagedResourceCollection(resourceCollection)));
userService.getAllUsersByAge(35).subscribe((users: PagedResourceCollection<User>) => {
expect(users).toBeTruthy();
expect(users.resources).toBeTruthy();
expect(users.resources[0]).toBeTruthy();
expect(users.resources[0].id).toEqual('1');
});
});
});If you prefer to use @ngneat/spectator for testing, you can do that in the following way:
import {
HateoasResourceService,
NgxHateoasClientModule,
PagedResourceCollection,
ResourceCollection,
provideNgxHateoasClientTesting
} from '@lagoshny/ngx-hateoas-client';
import { of } from 'rxjs';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
describe('UserServiceTest', () => {
let spectator: SpectatorService<UserService>;
const createService = createServiceFactory({
providers: [provideNgxHateoasClientTesting()],
service: UserService,
mocks: [HateoasResourceService]
});
beforeEach(() => {
spectator = createService();
});
it('should init service', () => {
const userService = spectator.inject(UserService);
expect(userService).toBeTruthy();
});
it('should create new user', waitForAsync(() => {
const userService = spectator.inject(UserService);
const newUser = new User();
newUser.id = '1';
const hateoasResourceServiceMock = spectator.inject(HateoasResourceService);
hateoasResourceServiceMock.createResource.and.returnValue(of(newUser));
const user = new User();
user.name = 'Test user';
userService.create(user).subscribe((createdUser: User) => {
expect(createdUser).toBeTruthy();
expect(createdUser.id).toEqual('1');
});
}));
it('should return paged user list', waitForAsync(() => {
const userService = spectator.inject(UserService);
const returnedUser = new User();
returnedUser.id = '1';
const resourceCollection = new ResourceCollection<User>();
resourceCollection.resources = [returnedUser];
const hateoasResourceServiceMock = spectator.inject(HateoasResourceService);
hateoasResourceServiceMock.searchPage.and.returnValue(of(new PagedResourceCollection(resourceCollection)));
userService.getAllUsersByAge(35).subscribe((users: PagedResourceCollection<User>) => {
expect(users).toBeTruthy();
expect(users.resources).toBeTruthy();
expect(users.resources[0]).toBeTruthy();
expect(users.resources[0].id).toEqual('1');
});
}));
});There are several types of resources: the main resource type is Resource represents the server-side entity model class. If the server-side model has Embeddable entity type then use EmbeddedResource type instead Resource type.
Both Resource and EmbeddedResource have some the same methods therefore they have common parent BaseResource class implements these methods.
Also, you can create Resource class to represent resource projection, see more about resource projection here.
Each
Resource/EmbeddedResourcehas decorators @HateoasResource/@HateoasEmbeddedResource respectively. They're used to register info about your resources inhateoas-client. For example,resourceNamethat used to build resource requests.
To work with resource collections uses ResourceCollection type its holds an array of the resources. When you have a paged collection of resources result use an extension of ResourceCollection is PagedResourceCollection that allows you to navigate by pages and perform custom page requests.
In some cases, the server-side can have an entity inheritance model how to work with entity subtypes, you can found here.
@HateoasResource decorator use to register your Resource classes in hateoas-client with passed resourceName as decorator's param.
resourceName:stringshould be equals to the server-side resource name that uses to represent self resource link.options:ResourceOptionadditional resource options. Find more about these options here.
Using to specify resource name.
For example, you need to work with Shopresource:
import { Resource, HateoasResource } from '@lagoshny/ngx-hateoas-client';
@HateoasResource('shops')
export class Shop extends Resource {
...
}It means that server-side use shops as resource name for Shop entity and it is resource self-link seem like: http://localhost:8080/api/v1/shops (with the assumption that server's root URL is http://localhost:8080/api/v1)
It is required to mark your
Resourceclasses with this decorator otherwise you will get an error when performing resource request
ResourceOption contains properties:
{
routeName: string; // name of Resource route
}If you want to use special URL to get Resource use options#routeName param:
import { Resource, HateoasResource } from '@lagoshny/ngx-hateoas-client';
@HateoasResource('shops', { routeName: 'yourRoute' })
export class Shop extends Resource {
...
}Make sure that you configure route with name yourRoute in lib configuration, see http section.
If you not specify
routerNameit will be used default router name asdefaultRouter.
@HateoasEmbeddedResource decorator use to register your EmbeddedResource classes in hateoas-client with passed relationNames as decorator's param.
relationNamesis an array of the names where each is the name of relation with whichEmbeddedResourceusing inResourceclass.
For example, you have Client resource that using Address embedded resource:
import { EmbeddedResource, Resource, HateoasEmbeddedResource, HateoasResource } from '@lagoshny/ngx-hateoas-client';
@HateoasEmbeddedResource(['clientAddress'])
export class Address extends EmbeddedResource {
...
}
@HateoasResource('clients')
export class Client extends Resource {
...
public clientAddress: Address;
...
}In this case, @HateoasEmbeddedResource.relationNames has one element (clientAddress) that equals to property Address name in Client resource.
If embedded resource Address will use in several resources then @HateoasEmbeddedResource.relationNames should have all different property Address names that used in these resources.
It is required to mark your
EmbeddedResourceclasses with this decorator if wou want get concrete embedded resource class (in this exampleAddressclass). Otherwise you will get a warning about creating an embedded resource when parsing server's answer with the defaultEmbeddedResourceclass when performing resource request that using an embedded resource.
@HateoasProjection decorator use to register your projection resource classes in hateoas-client with passed resourceType and projectionName as decorator's params.
resourceTypeequals to resource type that use this resource projection.projectionNameshould be equals to the server-side resource projection name.
For example, you have Shop resource projection with the name shopProjection:
import { Resource, HateoasResource, HateoasProjection } from '@lagoshny/ngx-hateoas-client';
@HateoasResource('shops')
export class Shop extends Resource {
...
}
@HateoasProjection(Shop, 'shopProjection')
export class ShopProjection extends Resource {
...
}Using @HateoasProjection you can create separate resource projection classes with desired resource properties and relations.
For resource projection relations to another resource, you need to wrap these relations with ProjectionRelType type and mark these relations with @ProjectionRel decorator with a relation resource type.
- ProjectionRelType will hide
Resource/EmbeddedResourcemethods for projection relation that will lead clear projection relation interface. - @ProjectionRel decorator will be used to create relation with concrete resource type when parsing server-side answer.
See more about resource projection support here.
@ProjectionRel decorator use to register projection relation resource type in hateoas-client with passed relationType as decorator's param.
relationTypeequals to resource type that used as a relation property in resource projection.
For example, you have Shop resource projection with the name shopProjection and this projection has relation to Cart resource:
import { Resource, HateoasResource, HateoasProjection, ProjectionRel, ProjectionRelType } from '@lagoshny/ngx-hateoas-client';
import { ProjectionRelType } from './declarations';
@HateoasResource('carts')
export class Cart extends Resource {
...
}
@HateoasProjection(Shop, 'shopProjection')
export class ShopProjection extends Resource {
...
@ProjectionRel(Cart)
public cart: ProjectionRelType<Cart>;
...
}Using @ProjectionRel decorator allows knowinghateoas-client which relation resource type needs to use when creating a resource from the serv-side answer.
Here used
ProjectionRelTypetype as a wrapper forCartresource class, to hideResource/EmbeddedResourcemethods in projection relation. See more here about this type.
Examples of usage resource relation methods rely on presets.
-
Resource classes are
import { Resource, HateoasResource } from '@lagoshny/ngx-hateoas-client'; @HateoasResource('carts') export class Cart extends Resource { public shop: Shop; public products: Array<Product>; public status: string; public client: PhysicalClient; } @HateoasResource('shops') export class Shop extends Resource { public name: string; public rating: number; } @HateoasResource('products') export class Product extends Resource { public name: string; public cost: number; public description: string; } @HateoasResource('clients') export class Client extends Resource { public address: string; } @HateoasResource('physicalClients') export class PhysicalClient extends Client { public fio: string; } @HateoasResource('juridicalClients') export class JuridicalClient extends Client { public inn: string; }
-
HateoasClientConfiguration:
provideNgxHateoasClient({ http: { rootUrl: 'http://localhost:8080/api/v1' }, useTypes: { resources: [Cart, Shop, Product, Client, PhysicalClient, JuridicalClient] } })
-
Suppose we have existed resources:
Cart: { "status": "New", "_links": { "self": { "href": "http://localhost:8080/api/v1/carts/1" }, "cart": { "href": "http://localhost:8080/api/v1/carts/1{?projection}", "templated": true }, "shop": { "href": "http://localhost:8080/api/v1/carts/1/shop" }, "products": { "href": "http://localhost:8080/api/v1/carts/1/products" }, "productsPage": { "href": "http://localhost:8080/api/v1/carts/1/productPage?page={page}&size={size}&sort={sort}&projection={projection}", "templated": true }, "client": { "href": "http://localhost:8080/api/v1/carts/1/client" }, "postExample": { "href": "http://localhost:8080/api/v1/cart/1/postExample" }, "putExample": { "href": "http://localhost:8080/api/v1/cart/1/putExample" }, "patchExample": { "href": "http://localhost:8080/api/v1/cart/1/patchExample" }, } } Shop: { "name": "Some Name", "ratings": 5 "_links": { "self": { "href": "http://localhost:8080/api/v1/shops/1" }, "shop": { "href": "http://localhost:8080/api/v1/shops/1" } } } Product: { "name": "Milk", "cost": 2, "description": "Some description" "_links": { "self": { "href": "http://localhost:8080/api/v1/produtcs/1" }, "produtc": { "href": "http://localhost:8080/api/v1/produtcs/1" } } } Client: { "fio": "Some fio", "_links": { "self": { "href": "http://localhost:8080/api/v1/physicalClients/1" }, "physicalClient": { "href": "http://localhost:8080/api/v1/physicalClients/1" } } }
Parent class for Resource and EmbeddedResource classes. Contains common resource methods to work with resource relations through resource links (see below).
Checks that passed relation name contains in relation _link array of the BaseResource.
Method signature:
hasRelation(relationName: string): boolean;
relationName- resource relation name used to check.return value-trueif resource has that relation,falseotherwise.
Getting resource relation object by relation name.
This method takes GetOption parameter with it you can pass projection param
Method signature:
getRelation<T extends BaseResource>(relationName: string, options?: GetOption): Observable<T>;
relationName- resource relation name used to get request URL.options- GetOption additional options applied to the request.return value- Resource with typeT.throws error- when required params are not valid or link not found by relation name or returned value is not Resource.
Examples of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/carts/1/shop
cart.getRelation<Shop>('shop')
.subscribe((shop: Shop) => {
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/carts/1/shop?projection=shopProjection&testParam=test&sort=name,ASC
cart.getRelation<Shop>('shop', {
params: {
testParam: 'test',
projection: 'shopProjection'
},
sort: {
name: 'ASC'
},
// useCache: true | false, by default true
})
.subscribe((shop: Shop) => {
// some logic
});Getting related resource collection by relation name.
This method takes GetOption parameter with it you can pass projection param.
Method signature:
getRelatedCollection<T extends ResourceCollection<BaseResource>>(relationName: string, options?: GetOption): Observable<T>;
relationName- resource relation name used to get request URL.options- GetOption additional options applied to the request.return value- ResourceCollection collection of resources with typeT.throws error- when required params are not valid or link not found by relation name or returned value is not ResourceCollection.
Examples of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/carts/1/products
cart.getRelatedCollection<ResourceCollection<Product>>('products')
.subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/carts/1/products?projection=productProjection&testParam=test&sort=name,ASC
cart.getRelatedCollection<ResourceCollection<Product>>('products', {
params: {
testParam: 'test',
projection: 'productProjection'
},
sort: {
name: 'ASC'
},
// useCache: true | false, by default true
})
.subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
});Getting related resource collection with pagination by relation name.
This method takes PagedGetOption parameter with it you can pass projection param (see below).
If do not pass
pageParamswithPagedGetOptionthen will be used default page options. Also, you can set up own default page params through configuration.
Method signature:
getRelatedPage<T extends PagedResourceCollection<BaseResource>>(relationName: string, options?: PagedGetOption): Observable<T>;
relationName- resource relation name used to get request URL.options- PagedGetOption additional options applied to the request, if not passedpageParamsthen used default page params.return value- PagedResourceCollection paged collection of resources with typeT.throws error- when required params are not valid or link not found by relation name or returned value is not PagedResourceCollection.
Examples of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/carts/1/productPage?page=0&size=20
cart.getRelatedPage<PagedResourceCollection<Product>>('productsPage')
.subscribe((page: PagedResourceCollection<Product>) => {
const products: Array<Product> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.customPage();
*/
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/carts/1/productPage?page=1&size=40&projection=productProjection&testParam=test&sort=name,ASC
cart.getRelatedPage<PagedResourceCollection<Product>>('productsPage', {
pageParams: {
page: 1,
size: 40
},
params: {
testParam: 'test',
projection: 'productProjection'
},
sort: {
name: 'ASC'
},
// useCache: true | false, by default true
})
.subscribe((page: PagedResourceCollection<Product>) => {
const products: Array<Product> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.customPage();
*/
});Performing POST request with request body by relation link URL.
Method signature:
postRelation(relationName: string, requestBody: RequestBody<any>, options?: RequestOption): Observable<HttpResponse<any> | any>;
relationName- resource relation name used to get request URL.requestBody- RequestBody contains request body and additional body options.options- RequestOption additional options applied to the request.return value- by defaultraw response dataor AngularHttpResponsewhenoptionsparam has aobserve: 'response'value.
Examples of usage (given the presets):
// Performing POST request by the URL: http://localhost:8080/api/v1/cart/1/postExample
cart.postRelation('postExample', {
// In this case null values in someBody will be ignored
body: someBody
})
.subscribe((rawResult: any) => {
// some logic
});With options:
// Performing POST request by the URL: http://localhost:8080/api/v1/cart/1/postExample?testParam=test
cart.postRelation('postExample', {
// In this case null values in someBody will be NOT ignored
body: someBody,
valuesOption: {
include: Include.NULL_VALUES
}
}, {
params: {
testParam: 'test'
},
observe: 'response'
})
.subscribe((response: HttpResponse<any>) => {
// some logic
});Performing PATCH request with request body by relation link URL.
Method signature:
patchRelation(relationName: string, requestBody: RequestBody<any>, options?: RequestOption): Observable<HttpResponse<any> | any>;
relationName- resource relation name used to get request URL.requestBody- RequestBody contains request body and additional body options.options- RequestOption additional options applied to the request.return value- by defaultraw response dataor AngularHttpResponsewhenoptionsparam has aobserve: 'response'value.
Examples of usage (given the presets):
// Performing PATCH request by the URL: http://localhost:8080/api/v1/cart/1/patchExample
cart.patchRelation('patchExample', {
// In this case null values in someBody will be ignored
body: someBody
})
.subscribe((rawResult: any) => {
// some logic
});With options:
// Performing PATCH request by the URL: http://localhost:8080/api/v1/cart/1/patchExample?testParam=test
cart.patchRelation('patchExample', {
// In this case null values in someBody will be NOT ignored
body: someBody,
valuesOption: {
include: Include.NULL_VALUES
}
}, {
params: {
testParam: 'test'
},
observe: 'response'
})
.subscribe((response: HttpResponse<any>) => {
// some logic
});Performing PUT request with request body by relation link URL.
Method signature:
putRelation(relationName: string, requestBody: RequestBody<any>, options?: RequestOption): Observable<HttpResponse<any> | any>;
relationName- resource relation name used to get request URL.requestBody- RequestBody contains request body and additional body options.options- RequestOption additional options applied to the request.return value- by defaultraw response dataor AngularHttpResponsewhenoptionsparam has aobserve: 'response'value.
Examples of usage (given the presets):
// Performing PUT request by the URL: http://localhost:8080/api/v1/cart/1/putExample
cart.putRelation('putExample', {
// In this case null values in someBody will be ignored
body: someBody
})
.subscribe((rawResult: any) => {
// some logic
});With options:
// Performing PUT request by the URL: http://localhost:8080/api/v1/cart/1/putExample?testParam=test
cart.putRelation('putExample', {
// In this case null values in someBody will be NOT ignored
body: someBody,
valuesOption: {
include: Include.NULL_VALUES
}
}, {
params: {
testParam: 'test'
},
observe: 'response'
})
.subscribe((response: HttpResponse<any>) => {
// some logic
});Main resource class. Extend model classes with Resource class to have the ability to use resource methods.
The difference between the Resource type and EmbeddedResource is Resource class has a self link therefore it has an id property, EmbeddedResource has not.
Resource classes are @Entity server-side classes. EmbeddedResource classes are @Embeddable entities.
Resource class extend BaseResource with additional resource relations methods that used only with Resource type.
Each
Resourceclass should be declared with @HateoasResource decorator.
Adding passed entities to the resource collection behind the relation name.
Used POST method with 'Content-Type': 'text/uri-list'.
This method DOES NOT REPLACED existing resources in the collection instead it adds new ones. To replace collection resource with passed entities use bindRelation method.
Method signature:
addCollectionRelation<T extends Resource>(relationName: string, entities: Array<T>): Observable<HttpResponse<any>>;
relationName- resource relation name used to get request URL mapped to resource collection.entities- an array of entities that should be added to resource collection.return value- AngularHttpResponseresult.
Examples of usage (given the presets):
// Suppose product1 already exists with id = 1
const product1 = ...;
// Suppose product2 already exists with id = 2
const product2 = ...;
cart.addCollectionRelation('products', [product1, product2])
/*
Performing POST request by the URL: http://localhost:8080/api/v1/carts/1/products
Content-type: 'text/uri-list'
Body: [http://localhost:8080/api/v1/products/1, http://localhost:8080/api/v1/products/2]
*/
.subscribe((result: HttpResponse<any>) => {
// some logic
});Bounding the passed entity or collection of entities to this resource by the relation name.
Used PUT method with 'Content-Type': 'text/uri-list'.
This method also REPLACED existing resources in the collection by passed entities. To add entities to collection resource use addCollectionRelation method.
Method signature:
bindRelation<T extends Resource>(relationName: string, entities: T | Array<T>): Observable<HttpResponse<any>>;
relationName- resource relation name used to get request URL.entities- an array of entities that should be bound to resource.return value- AngularHttpResponseresult.
Examples of usage (given the presets):
With single resource relation:
// Suppose shopToBind already exists with id = 1
const shopToBind = ...;
cart.bindRelation('shop', shopToBind)
/*
Performing PUT request by the URL: http://localhost:8080/api/v1/carts/1/shop
Content-type: 'text/uri-list'
Body: http://localhost:8080/api/v1/shops/1
*/
.subscribe((result: HttpResponse<any>) => {
// some logic
});With collection resource relation:
// Suppose product1 already exists with id = 1
const product1 = ...;
// Suppose product2 already exists with id = 2
const product2 = ...;
cart.bindRelation('products', [product1, product2])
/*
Performing PUT request by the URL: http://localhost:8080/api/v1/carts/1/products
Content-type: 'text/uri-list'
Body: [http://localhost:8080/api/v1/products/1, http://localhost:8080/api/v1/products/2]
*/
.subscribe((result: HttpResponse<any>) => {
// some logic
});Unbinding single resource relation behind resource name.
Used DELETE method to relation resource link URL.
This method does not work with collection resource relations. To unbind collection resource relation use UnbindCollectionRelation method. To delete one resource from resource collection use deleteRelation method.
Method signature:
unbindRelation<T extends Resource>(relationName: string): Observable<HttpResponse<any>>;
relationName- resource relation name to unbind.return value- AngularHttpResponseresult.
Examples of usage (given the presets):
// Suppose cart already bound shop resource by relation name 'shop'
cart.unbindRelation('shop')
/*
Performing DELETE request by the URL: http://localhost:8080/api/v1/carts/1/shop
*/
.subscribe((result: HttpResponse<any>) => {
// some logic
});Unbinding all resources from resource collection behind resource name.
Used PUT method with 'Content-Type': 'text/uri-list' and EMPTY body to clear relations.
This method does not work with SINGLE resource relations. To delete single resource relations use unbindRelation or deleteRelation methods. To delete one resource from collection use deleteRelation method.
Method signature:
unbindCollectionRelation<T extends Resource>(relationName: string): Observable<HttpResponse<any>>;
relationName- resource relation name used to get request URL.return value- AngularHttpResponseresult.
Examples of usage (given the presets):
/*
Performing PUT request by the URL: http://localhost:8080/api/v1/carts/1/products
Content-type: 'text/uri-list'
Body: ''
*/
cart.unbindCollectionRelation('products')
.subscribe((result: HttpResponse<any>) => {
// some logic
});Deleting resource relation.
For collection, means that only passed entity will be unbind from the collection. For single resource, deleting relation the same as unbindRelation method.
To delete all resource relations from collection use unbindCollectionRelation method.
Method signature:
deleteRelation<T extends Resource>(relationName: string, entity: T): Observable<HttpResponse<any>>;
relationName- resource relation name used to get request URL.entity- entity to unbind.return value- AngularHttpResponseresult.
Examples of usage (given the presets):
// Performing DELETE request by the URL: http://localhost:8080/api/v1/carts/1/shop/1
// Suppose shopToDelete already exists with id = 1
const shopToDelete = ...;
cart.deleteRelation('shop', shopToDelete)
.subscribe((result: HttpResponse<any>) => {
// some logic
});This resource type uses when a server-side entity is @Embeddable. It means that this entity has not an id property and can't exist standalone.
Because embedded resources have not an id then it can use only BaseResource methods.
Each
EmbeddedResourceclass should be declared with @HateoasEmbeddedResource decorator.
This resource type represents collection of resources.
You can get this type as result GetRelatedCollection, GetResourceCollection or perform CustomQuery/CustomSearchQuery with passed return type as ResourceCollection.
Resource collection holds resources in the public property with the name resources.
This resource type represents paged collection of resources. You can get this type as result GetRelatedPage, GetPage or perform CustomQuery/CustomSearchQuery with passed return type as PagedResourceCollection.
PagedResourceCollection extends ResourceCollection type and adds methods to work with a page.
When you do not pass page or size params in methods with PagedGetOption then used default page values.
Also, you can set up own default page params through configuration.
Checks that PagedResourceCollection has the link to get the first-page result.
Method signature:
hasFirst(): boolean;
return value-truewhen the link to get the first page exists,falseotherwise.
Checks that PagedResourceCollection has the link to get the last page result.
Method signature:
hasLast(): boolean;
return value-truewhen the link to get the last page exists,falseotherwise.
Checks that PagedResourceCollection has the link to get the next page result.
Method signature:
hasNext(): boolean;
return value-truewhen the link to get the next page exists,falseotherwise.
Checks that PagedResourceCollection has the link to get the previous page result.
Method signature:
hasPrev(): boolean;
return value-truewhen the link to get the prev page exists,falseotherwise.
Performing a request to get the first-page result by the first-page link.
Method signature:
first(options?: {useCache: true;}): Observable<PagedResourceCollection<T>>;
options- additional options to manipulate the cache when getting a result (by default will be used the cache if it enabled in the configuration).return value- PagedResourceCollection with resource typesT.throws error- when the link to get the first-page result is not exist.
Suppose products have 3 pages with 20 resources per page, and the previous request was to get products with a page number = 1.
To get the first products page, will perform request to page number = 0 with current or default page size (if before page size not passed).
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=0&size=20
const pagedProductCollection = ...;
pagedProductCollection.first()
.subscribe((firstPageResult: PagedResourceCollection<Product>) => {
// firstPageResult can be fetched from the cache if before was performing the same request
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=0&size=20
const pagedProductCollection = ...;
pagedProductCollection.first({useCache: false})
.subscribe((firstPageResult: PagedResourceCollection<Product>) => {
// firstPageResult always will be fetched from the server because the cache is disabled for this request
// some logic
});Performing a request to get the last-page result by the last-page link.
Method signature:
last(options?: {useCache: true;}): Observable<PagedResourceCollection<T>>;
options- additional options to manipulate the cache when getting a result (by default will be used the cache if it enabled in the configuration).return value- PagedResourceCollection with resource typesT.throws error- when the link to get the last-page result is not exist.
Suppose products have 3 pages with 20 resources per page, and the previous request was to get products with a page number = 1.
To get the last products page, will perform request to page number = 2 with current or default page size (if before page size not passed).
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=2&size=20
const pagedProductCollection = ...;
pagedProductCollection.last()
.subscribe((lastPageResult: PagedResourceCollection<Product>) => {
// lastPageResult can be fetched from the cache if before was performing the same request
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=2&size=20
const pagedProductCollection = ...;
pagedProductCollection.last({useCache: false})
.subscribe((lastPageResult: PagedResourceCollection<Product>) => {
// lastPageResult always will be fetched from the server because the cache is disabled for this request
// some logic
});Performing a request to get the next-page result by the next-page link.
Method signature:
next(options?: {useCache: true;}): Observable<PagedResourceCollection<T>>;
options- additional options to manipulate the cache when getting a result (by default will be used the cache if it enabled in the configuration).return value- PagedResourceCollection with resource typesT.throws error- when the link to get the next-page result is not exist.
Suppose products have 3 pages with 20 resources per page, and the previous request was to get products with a page number = 1.
To get the next products page, will perform request to page number = 2 with current or default page size (if before page size not passed).
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=1&size=20
const pagedProductCollection = ...;
pagedProductCollection.next()
.subscribe((nextPageResult: PagedResourceCollection<Product>) => {
// nextPageResult can be fetched from the cache if before was performing the same request
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=1&size=20
const pagedProductCollection = ...;
pagedProductCollection.next({useCache: false})
.subscribe((nextPageResult: PagedResourceCollection<Product>) => {
// nextPageResult always will be fetched from the server because the cache is disabled for this request
// some logic
});Performing a request to get the prev-page result by the prev-page link.
Method signature:
prev(options?: {useCache: true;}): Observable<PagedResourceCollection<T>>;
options- additional options to manipulate the cache when getting a result (by default will be used the cache if it enabled in the configuration).return value- PagedResourceCollection with resource typesT.throws error- when the link to get the prev-page result is not exist.
Suppose products have 3 pages with 20 resources per page, and the previous request was to get products with a page number = 1.
To get the prev products page, will perform request to page number = 0 with current or default page size (if before page size not passed).
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=0&size=20
const pagedProductCollection = ...;
pagedProductCollection.prev()
.subscribe((prevPageResult: PagedResourceCollection<Product>) => {
// prevPageResult can be fetched from the cache if before was performing the same request
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=0&size=20
const pagedProductCollection = ...;
pagedProductCollection.prev({useCache: false})
.subscribe((prevPageResult: PagedResourceCollection<Product>) => {
// prevPageResult always will be fetched from the server because the cache is disabled for this request
// some logic
});Performing a request to get the page with passed page number and current or default page size (if before page size not passed).
To pass page number, page size, sort params together use customPage method.
Method signature:
page(pageNumber: number, options?: {useCache: true;}): Observable<PagedResourceCollection<T>>;
pageNumber- number of the page to get.options- additional options to manipulate the cache when getting a result (by default will be used the cache if it enabled in the configuration).return value- PagedResourceCollection with resource typesT.throws error- whenpageNumbergreater than total pages.
Suppose products have 5 pages with 20 resources per page, and the previous request was to get products with a page number = 1.
To get the products page = 3, will perform request to page number = 3 with current or default page size (if before page size not passed).
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=3&size=20
const pagedProductCollection = ...;
pagedProductCollection.page(3)
.subscribe((customPageResult: PagedResourceCollection<Product>) => {
// customPageResult can be fetched from the cache if before was performing the same request
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=3&size=20
const pagedProductCollection = ...;
pagedProductCollection.page(3, {useCache: false})
.subscribe((customPageResult: PagedResourceCollection<Product>) => {
// customPageResult always will be fetched from the server because the cache is disabled for this request
// some logic
});Performing a request to get the page with passed page size and page number reset to 0 value.
To pass page number, page size, sort params together use customPage method.
Method signature:
size(size: number, options?: {useCache: true;}): Observable<PagedResourceCollection<T>>;
size- count of resources to page.options- additional options to manipulate the cache when getting a result (by default will be used the cache if it enabled in the configuration).return value- PagedResourceCollection with resource typesT.throws error- whensizegreater than total count resources.
Suppose products have 5 pages with 20 resources per page, and the previous request was to get products with a page number = 1, size = 20.
To increase the current page size to 50, will perform a request to the page number = 0 and page size = 50.
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=0&size=50
const pagedProductCollection = ...;
pagedProductCollection.size(50)
.subscribe((customPageResult: PagedResourceCollection<Product>) => {
// customPageResult can be fetched from the cache if before was performing the same request
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=0&size=50
const pagedProductCollection = ...;
pagedProductCollection.size(50, {useCache: false})
.subscribe((customPageResult: PagedResourceCollection<Product>) => {
// customPageResult always will be fetched from the server because the cache is disabled for this request
// some logic
});Sorting the current page result.
To pass page number, page size, sort params together use customPage method.
Method signature:
sortElements(sortParam: Sort, options?: {useCache: true;}): Observable<PagedResourceCollection<T>>;
sortParam- Sort params.options- additional options to manipulate the cache when getting a result (by default will be used the cache if it enabled in the configuration).return value- PagedResourceCollection with resource typesT.
Suppose products have 5 pages with 20 resources per page, and the previous request was to get products with a page number = 1, size = 20.
To sort the current page result, will perform a request to the current page number with the current page size and passed sort params.
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=1&size=20&sort=cost,ASC&sort=name,DESC
const pagedProductCollection = ...;
pagedProductCollection.sortElements({cost: 'ASC', name: 'DESC'})
.subscribe((customPageResult: PagedResourceCollection<Product>) => {
// customPageResult can be fetched from the cache if before was performing the same request
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=1&size=20&sort=cost,ASC&sort=name,DESC
const pagedProductCollection = ...;
pagedProductCollection.sortElements({cost: 'ASC', name: 'DESC'}, {useCache: false})
.subscribe((customPageResult: PagedResourceCollection<Product>) => {
// customPageResult always will be fetched from the server because the cache is disabled for this request
// some logic
});Performing a request to get the page with custom page number, page size, and sort params.
Method signature:
customPage(params: SortedPageParam, options?: {useCache: true;}): Observable<PagedResourceCollection<T>>;
params- SortedPageParam page and sort params.options- additional options to manipulate the cache when getting a result (by default will be used the cache if it enabled in the configuration).return value- PagedResourceCollection with resource typesT.throws error- when the page size, greater than total count resources or page number greater than total pages.
When pass only part of the
paramsthen used default page params for not passed ones.
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=2&size=30&sort=name,ASC
const pagedProductCollection = ...;
pagedProductCollection.customPage({
pageParams: {
page: 2,
size: 30
},
sort: {
name: 'ASC'
}
})
.subscribe(value1 => {
// customPageResult can be fetched from the cache if before was performing the same request
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=2&size=30&sort=name,ASC
const pagedProductCollection = ...;
pagedProductCollection.customPage({
pageParams: {
page: 2,
size: 30
},
sort: {
name: 'ASC'
}
},
{useCache: true})
.subscribe(value1 => {
// customPageResult always will be fetched from the server because the cache is disabled for this request
// some logic
});The library allows work with entities hierarchy.
Suppose exists the next resource's hierarchy:
import { Resource, HateoasResource } from '@lagoshny/ngx-hateoas-client';
@HateoasResource('carts')
export class Cart extends Resource {
public client: Client;
}
@HateoasResource('clients')
export class Client extends Resource {
public address: string;
}
@HateoasResource('physicalClients')
export class PhysicalClient extends Client {
public fio: string;
}
@HateoasResource('juridicalClients')
export class JuridicalClient extends Client {
public inn: string;
}
With hal-json representation:
Cart:
{
"status": "New",
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/carts/1"
},
"cart": {
"href": "http://localhost:8080/api/v1/carts/1{?projection}",
"templated": true
},
"client": {
"href": "http://localhost:8080/api/v1/carts/1/physicalClient"
}
}
}
PhysicalClient:
{
"fio": "Some fio",
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/physicalClients/1"
},
"physicalClient": {
"href": "http://localhost:8080/api/v1/physicalClients/1"
}
}
}Of course, resource types are set in the configuration useTypes section:
...
provideNgxHateoasClient({
useTypes: {
resources: [Cart, Client, PhysicalClient, JuridicalClient]
}
})
...From the example, above can note that the Cart resource has the client property with type Client.
In its turn, client can have one of the types PhysicalClient or JuridicalClient.
You can use instanceof statement to know what client resource type you got.
// Suppose exists cart resource and after getting client relation need to know what is the client type
const cart = ...
cart.getRelation('client')
.subscribe((client: Client) => {
if (client instanceof PhysicalClient) {
const physicalClient = client as PhysicalClient;
// some logic
} else if (client instanceof JuridicalClient) {
const juridicalClient = client as JuridicalClient;
// some logic
}
});Spring Data Rest allows creating resource projections. Projection can show resource relations with a resource object as inner objects (not only as links in resource relation links array).
Hateoas-client has support for these resource projections.
See more about Spring Data Rest projections here.
To create resource projection you need to create a projection class extend it with Resource and mark it with @HateaosProjection decorator passing projection resourceType and projectionName params.
resourceTypeshould be equals to resource type that use this projection.projectionNameshould be equals to the server-side resource projection name.
Use HateoasResourceService to perform projection request with first param as projection type for all the methods. You can also create custom resource service to concreate projection type.
If your projection has property relations to resource classes then you should decorate these properties with @ProjectionRel decorator passing resourceType param.
Besides need to wrap property resource type with ProjectionRelType type to hide Resource/EmbeddedResource methods (see example below).
As mentioned earlier that projection relations that resources are plain JSON objects. This means that these objects have not resources relations links array and other signs of the resource. But in your code when you will use existing resources classes as a relation type in projection class you will have all
Resource/EmbeddedResourcemethods that can be applied only to resource type. To prevent this behavior you need all projection resource relation types to wrap with ProjectionRelType type.
Resource projection example:
Suppose, you have a projection with the name cartProjection for Cart resource:
import { Resource, HateoasResource, HateoasProjection, ProjectionRel, ProjectionRelType } from '@lagoshny/ngx-hateoas-client';
@HateoasResource('carts')
export class Cart extends Resource {
...
public shop: Shop;
...
}
@HateoasResource('shops')
export class Shop extends Resource {
...
public shopName: string;
public printShopName() {
console.log(this.shopName)
}
...
}
@HateoasProjection(Cart, 'cartProjection')
export class CartProjection extends Resource {
...
@ProjectionRel(Shop)
public shop: ProjectionRelType<Shop>;
...
}Usages:
import { HateoasResourceService } from '@lagoshny/ngx-hateoas-client';
...
export class CartComponent {
constructor(private reosurceService: HateoasResourceService) {
}
public getCartProjection(): CartProjection {
this.reosurceService.getResource(CartProjection, 1)
.subscribe((cartProjection: CartProjection) => {
// Will print `true` because cartProjection.shop is Shop type as was declared with @ProjectionRel decorator
console.log(cartProjection.shop instanceof Shop);
// Will print the shop name to the console
cartProjection.shop.printShopName();
// Will not compile because ProjectionRelType<Shop> has not Resource/EmbeddedResource methods
cartProjection.shop.getRelation(...);
})
}
}
...In this case, CartProjection.shop is a simple object with the type Shop that has only Shop class properties and methods without Resource/EmbeddedResource methods.
Resource projection classes can be used with
HateoasResourceServicemethods, you need to pass resource projection type as the first methods param instead of a simple resource type.
This is a special type that allows wrapping your resource relations properties in resource projection to hide all Reosurce/EmbeddedResource methods.
Usages:
import { Resource, HateoasResource, HateoasProjection, ProjectionRel, ProjectionRelType } from '@lagoshny/ngx-hateoas-client';
@HateoasResource('carts')
export class Cart extends Resource {
...
public shop: Shop;
...
}
@HateoasResource('shops')
export class Shop extends Resource {
}
@HateoasProjection(Cart, 'cartProjection')
export class CartProjection extends Resource {
...
@ProjectionRel(Shop)
public shop: ProjectionRelType<Shop>;
...
}Here CartProjection.shop will show only Shop type properties and methods. Methods from Resource will be hidden.
As described before to work with resources you can use built-in HateoasResourceService or create custom resource service.
Difference in methods signature between built-in HateoasResourceService and custom resource service is built-in service always has a resource type as the first method param.
Examples of usage resource service methods rely on this presets.
-
Resource class is
import { Resource, HateoasResource } from '@lagoshny/ngx-hateoas-client'; @HateoasResource('products') export class Product extends Resource { public name: string; public cost: number; public description: string; public type: ProductType; } @HateoasResource('productTypes') export class ProductType extends Resource { public name: string; }
-
Resource projection class is
import { Resource, HateoasProjection, ProjectionRel, ProjectionRelType } from '@lagoshny/ngx-hateoas-client'; @HateoasProjection(Product, 'productProjection') export class ProuctProjection extends Resource { public name: string; public cost: number; public description: string; @ProjectionRel(ProductType) public type: ProjectionRelType<ProductType>; }
-
HateoasClientConfiguration:
provideNgxHateoasClient({ http: { rootUrl: 'http://localhost:8080/api/v1' }, useTypes: { resources: [Product, ProductType, ProuctProjection] } })
-
Resource service as built-in HateoasResourceService is
@Component({ ... }) export class AppComponent { constructor(private resourceHateoasService: HateoasResourceService) { } }
-
Resource service as custom resource service is
import { HateoasResourceOperation, HateoasResourceService, PagedResourceCollection, ResourceCollection} from '@lagoshny/ngx-hateoas-client'; @Injectable({providedIn: 'root'}) export class ProductService extends HateoasResourceOperation<Product> { constructor(private resourceHateoasService: HateoasResourceService) { super(Product); } public getProductProjection(id: number): Observable<ProductProjection> { return this.resourceHateoasService.getResource(ProductProjection, id); } public getProductProjections(): Observable<ResourceCollection<ProductProjection>> { return this.resourceHateoasService.getCollection(ProductProjection); } public getPagedProductProjections(): Observable<PagedResourceCollection<ProductProjection>> { return this.resourceHateoasService.getPage(ProductProjection); } public searchProductProjection(searchQuery: string): Observable<ProductProjection> { return this.resourceHateoasService.searchResource(ProductProjection, searchQuery); } public searchProductProjections(searchQuery: string): Observable<ResourceCollection<ProductProjection>> { return this.resourceHateoasService.searchCollection(ProductProjection, searchQuery); } public searchPagedProductProjections(searchQuery: string): Observable<PagedResourceCollection<ProductProjection>> { return this.resourceHateoasService.searchPage(ProductProjection, searchQuery); } } @Component({ ... }) export class AppComponent { constructor(private productService: ProductService) { } }
No matter which service used (custom or built-in) both have the same resource methods.
Getting one resource Resource. This method takes GetOption parameter type.
To get resource projection instead of a resource type pass resource projection type in first method param when used
resourceHateoasService. See more about projection support here.
Method signature:
getResource(id: number | string, options?: GetOption): Observable<T>;
id- resource id to get.options- GetOption additional options applied to the request.return value- Resource with typeT.throws errorwhen returned value is not Resource.
Example of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/products/1
this.productService.getResource(1)
.subscribe((product: Product) => {
// some logic
})
this.resourceHateoasService.getResource(Product, 1)
.subscribe((product: Product) => {
// some logic
})With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products/1?testParam=test&sort=cost,ASC
this.productService.getResource(1, {
params: {
testParam: 'test'
},
sort: {
cost: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((product: Product) => {
// some logic
})
this.resourceHateoasService.getResource(Product, 1, {
params: {
testParam: 'test'
},
sort: {
cost: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((product: Product) => {
// some logic
})Get projection example:
// Performing GET request by the URL: http://localhost:8080/api/v1/products/1?projection=productProjection
this.productService.getProductProjection(1)
.subscribe((productProjection: ProductProjection) => {
// some logic
})
this.resourceHateoasService.getResource(ProductProjection, 1)
.subscribe((productProjection: ProductProjection) => {
// some logic
})Getting collection of resources ResourceCollection. This method takes GetOption parameter type.
To get resource projection instead of a resource type pass resource projection type in first method param when used
resourceHateoasService. See more about projection support here.
Method signature:
getCollection(options?: GetOption): Observable<ResourceCollection<T>>;
options- GetOption additional options applied to the request.return value- ResourceCollection collection of resources with typeT.throws errorwhen returned value is not ResourceCollection.
Example of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/products
this.productService.getCollection()
.subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
})
this.resourceHateoasService.getCollection(Product)
.subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
})With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?testParam=test&sort=cost,ASC
this.productService.getCollection({
params: {
testParam: 'test'
},
sort: {
cost: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
})
this.resourceHateoasService.getCollection(Producr, {
params: {
testParam: 'test'
},
sort: {
cost: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
})Get projection example:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?projection=productProjection
this.productService.getProductProjections()
.subscribe((productProjections: ResourceCollection<ProductProjection>) => {
// some logic
})
this.resourceHateoasService.getCollection(ProductProjection)
.subscribe((productProjections: ResourceCollection<ProductProjection>) => {
// some logic
})Getting paged collection of resources PagedResourceCollection. This method takes PagedGetOption parameter type.
To get resource projection instead of a resource type pass resource projection type in first method param when used
resourceHateoasService. See more about projection support here.
If do not pass
pageParamswithPagedGetOptionthen will be used default page options. Also, you can set up own default page params through configuration.
Method signature:
getPage(options?: PagedGetOption): Observable<PagedResourceCollection<T>>;
options- PagedGetOption additional options applied to the request, if not passedpageParamsthen used default page params.return value- PagedResourceCollection paged collection of resources with typeT.throws errorwhen returned value is not PagedResourceCollection
Example of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/products?page=0&size=20
this.productService.getPage()
.subscribe((page: PagedResourceCollection<Product>) => {
const products: Array<Product> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
});
this.resourceHateoasService.getPage(Product)
.subscribe((page: PagedResourceCollection<Product>) => {
const products: Array<Product> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?testParam=test&page=1&size=40&sort=cost,ASC
this.productService.getPage({
pageParams: {
page: 1,
size: 40
},
params: {
testParam: 'test'
},
sort: {
cost: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((page: PagedResourceCollection<Product>) => {
const products: Array<Product> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
});
this.resourceHateoasService.getPage(Product, {
pageParams: {
page: 1,
size: 40
},
params: {
testParam: 'test'
},
sort: {
cost: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((page: PagedResourceCollection<Product>) => {
const products: Array<Product> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
});Get projection example:
// Performing GET request by the URL: http://localhost:8080/api/v1/products?projection=productProjection
this.productService.getPagedProductProjections()
.subscribe((page: PagedResourceCollection<ProductProjection>) => {
const products: Array<ProductProjection> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
})
this.resourceHateoasService.getPage(ProductProjection)
.subscribe((page: PagedResourceCollection<ProductProjection>) => {
const products: Array<ProductProjection> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
})Creating new resource Resource.
Method signature:
createResource(requestBody: RequestBody<T>, options?: RequestOption): Observable<T | any>;
requestBody- RequestBody contains request body (in this case resource object) and additional body options.options- (optional) RequestOption that should be applied to the requestreturn value- Resource with typeTorraw response datawhen returned value is not resource object.
Example of usage (given the presets):
/*
Performing POST request by the URL: http://localhost:8080/api/v1/products
Request body:
{
"name": "Apple",
"cost": 100
}
Note: description is not passed because by default null values ignore. If you want pass description = null value then need to pass additional valuesOption params.
*/
const newProduct = new Product();
newProduct.cost = 100;
newProduct.name = 'Apple';
newProduct.description = null;
this.productService.createResource({
body: newProduct
}).subscribe((createdProduct: Product) => {
// some logic
});
this.resourceHateoasService.createResource(Product, {
body: newProduct
}).subscribe((createdProduct: Product) => {
// some logic
});With options:
/*
Performing POST request by the URL: http://localhost:8080/api/v1/products
Request body:
{
"name": "Apple",
"cost": 100,
"description": null
}
Note: description is passed with null value because valuesOption = Include.NULL_VALUES was passed.
*/
const newProduct = new Product();
newProduct.cost = 100;
newProduct.name = 'Apple';
newProduct.description = null;
this.productService.createResource({
body: newProduct,
valuesOption: {
include: Include.NULL_VALUES
}
}).subscribe((createdProduct: Product) => {
// some logic
});
this.resourceHateoasService.createResource(Product, {
body: newProduct,
valuesOption: {
include: Include.NULL_VALUES
}
}).subscribe((createdProduct: Product) => {
// some logic
});Updating all values of an existing resource at once by resource self link URL.
For not passed values of resource,
nullvalues will be used.
To update a resource performs PUT request by the URL equals to resource self link passed in entity param.
To update a resource by an id directly use UpdateResourceById.
To update part of the values of resource use PatchResource method.
Method signature:
updateResource(entity: T, requestBody?: RequestBody<any>, options?: RequestOption): Observable<T | any>;
entity- resource to update.requestBody- RequestBody contains request body (in this case new values for resource) and additional body options.options- (optional) RequestOption that should be applied to the requestreturn value- Resource with typeTorraw response datawhen returned value is not resource object.
When passed only
entityparam then values ofentitywill be used to update values of resource.
Example of usage (given the presets):
/*
Suppose exitsProduct has a self link = http://localhost:8080/api/v1/products/1
Performing PUT request by the URL: http://localhost:8080/api/v1/products/1
Request body:
{
"name" = exitsProduct.name,
"cost": 500
}
Note:
1) Description is not passed because by default null values ignore. If you want pass description = null value then need to pass additional valuesOption params.
2) Since update resource updating all resource values at once and for description value is not passing then the server-side can overwrite description to null.
*/
const exitsProduct = ...;
exitsProduct.cost = 500;
exitsProduct.description = null;
this.productService.updateResource(exitsProduct)
.subscribe((updatedProduct: Product) => {
// some logic
});
// For resourceHateoasService this snippet is identicalWith options:
/*
Suppose exitsProduct has a self link = http://localhost:8080/api/v1/products/1
Performing PUT request by the URL: http://localhost:8080/api/v1/products/1
Request body:
{
"name": null,
"cost": 500
}
Note:
1) Name was passed with null value because valuesOption = Include.NULL_VALUES was passed.
2) Since update resource updating all resource values at once and for description value is not passing then the server-side can overwrite description to null.
*/
const exitsProduct = ...;
this.productService.updateResource(exitsProduct, {
body: {
name: null,
cost: 500
},
valuesOption: {
include: Include.NULL_VALUES
}
}).subscribe((updatedProduct: Product) => {
// some logic
});
// For resourceHateoasService this snippet is identicalUpdating all values of an existing resource at once by resource id.
To update a resource by resource self link URL use UpdateResource.
To update part of the values of resource use PatchResource method.
Method signature:
updateResourceById(id: number | string, requestBody: RequestBody<any>, options?: RequestOption): Observable<T | any>;
id- resource id to update.requestBody- RequestBody contains request body (in this case new values for resource) and additional body options.options- (optional) RequestOption that should be applied to the requestreturn value- Resource with typeTorraw response datawhen returned value is not resource object.
Example of usage (given the presets):
/*
Suppose exitsProduct has an id = 1
Performing PUT request by the URL: http://localhost:8080/api/v1/products/1
Request body:
{
"name" = exitsProduct.name,
"cost": 500
}
Note:
1) Description is not passed because by default null values ignore. If you want pass description = null value then need to pass additional valuesOption params.
2) Since update resource updating all resource values at once and for description value is not passing then the server-side can overwrite description to null.
*/
const exitsProduct = ...;
exitsProduct.cost = 500;
exitsProduct.description = null;
this.productService.updateResourceById(1, {
body: {
...exitsProduct
}
})
.subscribe((updatedProduct: Product) => {
// some logic
});
this.resourceHateoasService.updateResourceById(Product, 1, {
body: {
...exitsProduct
}
})
.subscribe((updatedProduct: Product) => {
// some logic
});With options:
/*
Suppose exitsProduct has an id = 1
Performing PUT request by the URL: http://localhost:8080/api/v1/products/1
Request body:
{
"name": null,
"cost": 500
}
Note:
1) Name was passed with null value because valuesOption = Include.NULL_VALUES was passed.
2) Since update resource updating all resource values at once and for description value is not passing then the server-side can overwrite description to null.
*/
this.productService.updateResourceById(1, {
body: {
name: null,
cost: 500
},
valuesOption: {
include: Include.NULL_VALUES
}
})
.subscribe((updatedProduct: Product) => {
// some logic
});
this.resourceHateoasService.updateResourceById(Product, 1, {
body: {
name: null,
cost: 500
},
valuesOption: {
include: Include.NULL_VALUES
}
})
.subscribe((updatedProduct: Product) => {
// some logic
});Patching part values of an existing resource by resource self link URL.
To patch a resource performs PATCH request by the URL equals to resource self link passed in entity param.
To patch a resource by an id directly use PatchResourceById.
To update all values of the resource at once use UpdateResource method.
Method signature:
patchResource(entity: T, requestBody?: RequestBody<any>, options?: RequestOption): Observable<T | any>;
entity- resource to patch.requestBody- RequestBody contains request body (in this case new values for resource) and additional body options.options- (optional) RequestOption that should be applied to the requestreturn value- Resource with typeTorraw response datawhen returned value is not resource object.
When passed only
entityparam then values ofentitywill be used to patch values of resource.
Example of usage (given the presets):
/*
Suppose exitsProduct has a self link = http://localhost:8080/api/v1/products/1
Performing PATCH request by the URL: http://localhost:8080/api/v1/products/1
Request body:
{
"name" = exitsProduct.name,
"cost": 500
}
Note:
1) Description is not passed because by default null values ignore. If you want pass description = null value then need to pass additional valuesOption params.
2) Since patch resource updating only part of resource values at once then all not passed values will have the old values.
*/
const exitsProduct = ...;
exitsProduct.cost = 500;
exitsProduct.description = null;
this.productService.patchResource(exitsProduct)
.subscribe((patchedProduct: Product) => {
// some logic
});
// For resourceHateoasService this snippet is identicalWith options:
/*
Suppose exitsProduct has a self link = http://localhost:8080/api/v1/products/1
Performing PATCH request by the URL: http://localhost:8080/api/v1/products/1
Request body:
{
"name": null,
"cost": 500
}
Note:
1) Name was passed with null value because valuesOption = Include.NULL_VALUES was passed.
2) Since patch resource updating only part of resource values at once then all not passed values will have the old values.
*/
const exitsProduct = ...;
this.productService.patchResource(exitsProduct, {
body: {
name: null,
cost: 500
},
valuesOption: {
include: Include.NULL_VALUES
}
}).subscribe((patchedProduct: Product) => {
// some logic
});
// For resourceHateoasService this snippet is identicalPatching part values of an existing resource by resource id.
To patch a resource by resource self link URL use UpdateResource.
To update all values of the resource at once use UpdateResource method.
Method signature:
patchResourceById(id: number | string, requestBody: RequestBody<any>, options?: RequestOption): Observable<T | any>;
id- resource id to patch.requestBody- RequestBody contains request body (in this case new values for resource) and additional body options.options- (optional) RequestOption that should be applied to the requestreturn value- Resource with typeTorraw response datawhen returned value is not resource object.
Example of usage (given the presets):
/*
Suppose exitsProduct has an id = 1
Performing PATCH request by the URL: http://localhost:8080/api/v1/products/1
Request body:
{
"name" = existProduct.name,
"cost": 500
}
Note:
1) Description is not passed because by default null values ignore. If you want pass description = null value then need to pass additional valuesOption params.
2) Since patch resource updating only part of resource values at once then all not passed values will have the old values.
*/
const exitsProduct = ...;
exitsProduct.cost = 500;
exitsProduct.description = null;
this.productService.patchResourceById(1, {
body: {
...exitsProduct
}
})
.subscribe((patchedProduct: Product) => {
// some logic
});
this.resourceHateoasService.patchResourceById(Product, 1, {
body: {
...exitsProduct
}
})
.subscribe((patchedProduct: Product) => {
// some logic
});With options:
/*
Suppose exitsProduct has an id = 1
Performing PUT request by the URL: http://localhost:8080/api/v1/products/1
Request body:
{
"name": null,
"cost": 500
}
Note:
1) Name was passed with null value because valuesOption = Include.NULL_VALUES was passed.
2) Since patch resource updating only part of resource values at once then all not passed values will have the old values.
*/
this.productService.patchResourceById(1, {
body: {
name: null,
cost: 500
},
valuesOption: {
include: Include.NULL_VALUES
}
})
.subscribe((patchedProduct: Product) => {
// some logic
});
this.resourceHateoasService.patchResourceById(Product, 1, {
body: {
name: null,
cost: 500
},
valuesOption: {
include: Include.NULL_VALUES
}
})
.subscribe((patchedProduct: Product) => {
// some logic
});Deleting resource by resource self link URL.
To delete a resource performs DELETE request by the URL equals to resource self link passed in entity param.
To delete a resource by an
iddirectly use DeleteResourceById.
Method signature:
deleteResource(entity: T, options?: RequestOption): Observable<HttpResponse<any> | any>;
entity- resource to delete.options- RequestOption additional options applied to the request.return value- by defaultraw response dataor AngularHttpResponsewhenoptionsparam has aobserve: 'response'value.
Example of usage (given the presets):
/*
Suppose exitsProduct has a self link = http://localhost:8080/api/v1/products/1
Will be perform DELETE request to the http://localhost:8080/api/v1/products/1
*/
this.productService.deleteResource(exitsProduct)
.subscribe((result: any) => {
// some logic
});
// For resourceHateoasService this snippet is identicalWith options:
/*
Suppose exitsProduct has a self link = http://localhost:8080/api/v1/products/1
Will be perform DELETE request to the http://localhost:8080/api/v1/products/1?testParam=test
*/
const exitsProduct = ...;
this.productService.deleteResource(exitsProduct, {
observe: 'response',
params: {
testParam: 'test'
}
})
.subscribe((result: HttpResponse<any>) => {
// some logic
});
// For resourceHateoasService this snippet is identicalDeleting resource by resource id.
To delete a resource by resource
self link URLuse DeleteResource.
Method signature:
deleteResourceById(id: number | string, options?: RequestOption): Observable<HttpResponse<any> | any>;
id- resource id to delete.options- RequestOption additional options applied to the request.return value- by defaultraw response dataor AngularHttpResponsewhenoptionsparam has aobserve: 'response'value.
Example of usage (given the presets):
/*
Suppose exitsProduct has an id = 1
Will be perform DELETE request to the http://localhost:8080/api/v1/products/1
*/
this.productService.deleteResourceById(1)
.subscribe((result: any) => {
// some logic
});
this.resourceHateoasService.deleteResourceById(Product, 1)
.subscribe((result: any) => {
// some logic
});With options:
/*
Suppose exitsProduct has an id = 1
Will be perform DELETE request to the http://localhost:8080/api/v1/products/1?testParam=test
*/
const exitsProduct = ...;
this.productService.deleteResourceById(1, {
observe: 'response',
params: {
testParam: 'test'
}
})
.subscribe((result: HttpResponse<any>) => {
// some logic
});
this.resourceHateoasService.deleteResourceById(Product, 1, {
observe: 'response',
params: {
testParam: 'test'
}
})
.subscribe((result: HttpResponse<any>) => {
// some logic
});Searching for one resource Resource.
This method takes GetOption parameter type.
To search resource projection instead of a resource type pass resource projection type in first method param when used
resourceHateoasService. See more about projection support here.
Method signature:
searchResource(searchQuery: string, options?: GetOption): Observable<T>;
searchQuery- additional part of the URL that follow after/search/resource URL.options- GetOption additional options applied to the request.return value- Resource with typeT.throws errorwhen returned value is not Resource
When using the method not need to pass
/search/part of the URL insearchQuery.
Example of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/searchQuery
this.productService.searchResource('searchQuery')
.subscribe((product: Product) => {
// some logic
});
this.resourceHateoasService.searchResource(Product, 'searchQuery')
.subscribe((product: Product) => {
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/byName?name=Fruit&sort=name,ASC
this.productService.searchResource('byName', {
params: {
name: 'Fruit'
},
sort: {
name: 'ASC'
},
// useCache: true | false, by default true
})
.subscribe((product: Product) => {
// some logic
});
this.resourceHateoasService.searchResource(Product, 'byName', {
params: {
name: 'Fruit'
},
sort: {
name: 'ASC'
},
// useCache: true | false, by default true
})
.subscribe((product: Product) => {
// some logic
});Get projection example:
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/searchQuery?projection=productProjection
this.productService.searchProductProjection()
.subscribe((productProjections: ProductProjection) => {
// some logic
})
this.resourceHateoasService.searchResource(ProductProjection, 'searchQuery')
.subscribe((productProjections: ProductProjection) => {
// some logic
})Searching for collection of resources ResourceCollection.
This method takes GetOption parameter type.
To search resource projection instead of a resource type pass resource projection type in first method param when used
resourceHateoasService. See more about projection support here.
Method signature:
searchCollection(searchQuery: string, options?: GetOption): Observable<ResourceCollection<T>>;
searchQuery- additional part of the URL that follow after/search/resource URL.options- GetOption additional options applied to the request.return value- ResourceCollection collection of resources with typeT.throws errorwhen returned value is not ResourceCollection
When using the method not need to pass
/search/part of the URL insearchQuery.
Example of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/searchQuery
this.productService.searchCollection('searchQuery')
.subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
});
this.resourceHateoasService.searchCollection(Product, 'searchQuery')
.subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/byName?name=Fruit&sort=name,ASC
this.productService.searchCollection('byName', {
params: {
name: 'Fruit'
},
sort: {
name: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
});
this.resourceHateoasService.searchCollection(Product, 'byName', {
params: {
name: 'Fruit'
},
sort: {
name: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((collection: ResourceCollection<Product>) => {
const products: Array<Product> = collection.resources;
// some logic
});Get projection example:
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/searchQuery?projection=productProjection
this.productService.searchProductProjections()
.subscribe((productProjections: ResourceCollection<ProductProjection>) => {
// some logic
})
this.resourceHateoasService.searchCollection(Product, 'searchQuery')
.subscribe((productProjections: ResourceCollection<ProductProjection>) => {
// some logic
})Searching for collection of resources with paginationPagedResourceCollection. This method takes PagedGetOption parameter type.
To search resource projection instead of a resource type pass resource projection type in first method param when used
resourceHateoasService. See more about projection support here.
If do not pass
pageParamswithPagedGetOptionthen will be used default page options. Also, you can set up own default page params through configuration.
Method signature:
searchPage(searchQuery: string, options?: PagedGetOption): Observable<PagedResourceCollection<T>>;
searchQuery- additional part of the URL that follow after/search/resource URL.options- PagedGetOption additional options applied to the request, if not passedpageParamsthen used default page params.return value- PagedResourceCollection paged collection of resources with typeT.throws errorwhen returned value is not PagedResourceCollection
When using the method not need to pass
/search/part of the URL insearchQuery.
Example of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/searchQuery?page=0&size=20
this.productService.searchPage('searchQuery')
.subscribe((pagedCollection: PagedResourceCollection<Product>) => {
const products: Array<Product> = pagedCollection.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
});
this.resourceHateoasService.searchPage(Product, 'searchQuery')
.subscribe((pagedCollection: PagedResourceCollection<Product>) => {
const products: Array<Product> = pagedCollection.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
});With options:
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/byName?name=Fruit&page=1&size=30&sort=name,ASC
this.productService.searchPage('byName', {
pageParams: {
page: 1,
size: 30
},
params: {
name: 'Fruit'
},
sort: {
name: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((pagedCollection: PagedResourceCollection<Product>) => {
const products: Array<Product> = pagedCollection.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
});
this.resourceHateoasService.searchPage(Product, 'byName', {
pageParams: {
page: 1,
size: 40
},
params: {
testParam: 'test'
},
sort: {
cost: 'ASC'
},
// useCache: true | false, by default true
}).subscribe((pagedCollection: PagedResourceCollection<Product>) => {
const products: Array<Product> = pagedCollection.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
});Get projection example:
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/searchQuery?page=0&size=20&projection=productProjection
this.productService.searchPagedProductProjections()
.subscribe((page: PagedResourceCollection<ProductProjection>) => {
const products: Array<ProductProjection> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
})
this.resourceHateoasService.searchPage(ProductProjection)
.subscribe((page: PagedResourceCollection<ProductProjection>) => {
const products: Array<ProductProjection> = page.resources;
/* can use page methods
page.first();
page.last();
page.next();
page.prev();
page.size(...);
page.page(...);
page.sortElements(...);
page.customPage(...);
*/
})Performing custom HTTP request for resource.
For example, use this method to perform a count query (see example below).
Method signature:
customQuery<R>(method: HttpMethod, query: string, requestBody?: RequestBody<any>, options?: PagedGetOption): Observable<R>;
method- HTTP request method (GET/POST/PUT/PATCH).query- additional part of the URL, added after root resource URL.requestBody- RequestBody uses whenmethodisPOST,PATCH,PUTto pass request body and additional body options.options- PagedGetOption additional options applied to the request.return value-anyobject that equals to passed generic type<R>
Example of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/countAll
this.productService.customQuery<number>(HttpMethod.GET, '/search/countAllBy')
.subscribe((count: number) => {
// some logic
});
this.resourceHateoasService.customQuery<number>(Product, HttpMethod.GET, '/search/countAllBy')
.subscribe((count: number) => {
// some logic
});Performing custom search HTTP request for resource.
For example, use this method to perform a count query (see example below).
Method signature:
customSearchQuery<R>(method: HttpMethod, searchQuery: string, requestBody?: RequestBody<any>, options?: PagedGetOption): Observable<R>;
method- HTTP request method (GET/POST/PUT/PATCH).searchQuery- additional part of the URL that follow after/search/resource URL.requestBody- RequestBody uses whenmethodisPOST,PATCH,PUTto pass request body and additional body options.options- PagedGetOption additional options applied to the request.return value-anyobject that equals to passed generic type<R>
Example of usage (given the presets):
// Performing GET request by the URL: http://localhost:8080/api/v1/products/search/countAll
this.productService.customSearchQuery<number>(HttpMethod.GET, '/countAllBy')
.subscribe((count: number) => {
// some logic
});
this.resourceHateoasService.customSearchQuery<number>(Product, HttpMethod.GET, '/countAllBy')
.subscribe((count: number) => {
// some logic
});This section describes library configuration params.
The library accepts configuration object:
http: ResourceRoute | MultipleResourceRoutes;
logs?: {
verboseLogs?: boolean;
};
cache?: {
enabled: boolean;
lifeTime?: number;
};
useTypes?: {
resources: Array<new (...args: any[]) => Resource>;
embeddedResources?: Array<new (...args: any[]) => EmbeddedResource>;
};
pagination?: {
defaultPage: {
size: number;
page?: number;
}
};
typesFormat?: {
date?: {
patterns: Array<string>;
}
};
isProduction?: boolean;If you want to use common URL to retrieve all Resources, use ResourceRoute http config:
http: {
[routeName1: string]: {
rootUrl: string; // (required) - defines root server URL that will be used to perform resource requests.
proxyUrl? : string; // (optional) - defines proxy URL that uses to change rootUrl to proxyUrl when getting a relation link.
},
....
[routeName2: string]: {
rootUrl: string; // (required) - defines root server URL that will be used to perform resource requests.
proxyUrl? : string; // (optional) - defines proxy URL that uses to change rootUrl to proxyUrl when getting a relation link.
};
}This creates default
Resource routewith namedefaultRoute. You don't need it specify in@HateoasResourceit would be done by default.
To configure several URLs use MultipleResourceRoutes:
http: {
// Use route name 'defaultRoute' to specify default route for all Resources
[routeName1: string]: {
rootUrl: string; // (required) - defines root server URL that will be used to perform resource requests.
proxyUrl? : string; // (optional) - defines proxy URL that uses to change rootUrl to proxyUrl when getting a relation link.
},
....
[routeName2: string]: {
rootUrl: string; // (required) - defines root server URL that will be used to perform resource requests.
proxyUrl? : string; // (optional) - defines proxy URL that uses to change rootUrl to proxyUrl when getting a relation link.
};
}After that, use routerName on Resource decorator @HateoasResource#options param.
To specify default
Resource routeuse route namedefaultRoute. This route used if no route name specified in @HateoasResource#options param.
verboseLogs- to debug lib works enable logging to the console. With enabled logging, all prepared resource stages will be printed.
See more about logging here.
enabled-trueto use cache forGETrequests,falseotherwise, default value istrue.mode- allows to adjust cache more granular usingCacheModemodes.lifeTime- default cache lifetime is 300 000 seconds (=5 minutes) pass new value to change default one.
CacheMode has two options:
ALWAYSis default option in this mode, all HTTP GET methods will use cache except methods where explicitly passeduseCache = false.ON_DEMANDis opposite mode,ALWAYSmode. Means that all HTTP GET methods will NOT use cache by default except methods where explicitly passeduseCache = true.
See more about caching here.
This configuration section uses to declare resource/embedded resource types that will be used to create resources with concrete resource classes when parsed server's answer.
For example, you use subtypes and you need to know which subtype is received from the server (see more about suptypes in this section).
If you do not declare this subtype type in useTypes.resources section you get the common Resource class type without your subtype class methods.
Because @HateoasResource/@HateoasEmbeddedResource decorators are used to registering heirs of Resource/EmbeddedResource classes in hateoas-client, then if you did not use your Resource class in your code
(i.e. there was no import of this resource in your code, it will be means that the decorator did not run), then you will receive a generic Resource type instead of a concrete resource type when parsed the server's answer.
When will it happen you will get warnings in the browser console.
To prevent it you need to declare this resource type in useTypes.resources section. This will run @HaeoasResource resource decorator and register your Resource type in hateoas-client.
The same logic applied to EmbeddedResource's. Use useTypes.embeddedResources to register your EmbeddedResource's type in hateoas-client.
It is recommend if all types of your resources (i.e. your classes that extended Resource/EmbeddedResource classes) will be declared in sections
useTypes.resourcesanduseTypes.embeddedResourcesrespectively.
resources- an array of the Resource types used to create concrete resource types when parsed server's answer instead of commonResourcetype.embeddedResources- an array of the EmbeddedResource types used to create concrete embedded resource types when parsed server's answer instead of commonEmbeddedResourcetype.
You can set the format for some types.
This format will be used when parse raw Resource JSON to determine which Resource property type should be used in the result Resource instance.
- Define patterns for the
Datetype. These patterns used when parsedResource JSONand if someResourceproperty match toDatepattern then it will have type asDatenot as rawstringtype. You can define array ofDatepatterns then ifResourceproperty match to one of them then it will be asDatetype.
date-fns lib is used as
Dateparser. See date pattern format here.
Example:
typesFormat: {
date: {
patterns: ['dd/MM/yyyy', 'MM/dd/yyyy'];
}
};For the Resource JSON like this:
{
"someDate": '23/06/2022',
"_links": {
...
}
}Resource instance will have property someDate as type Date.
Configuring HAL format that used to parse JSON as Resource.
Example:
halFormat: {
json: {
convertEmptyObjectToNull: true
},
collections: {
embeddedOptional: false
}
}Property json contains all format settings for json representation.
convertEmptyObjectToNull- by defaulttrue. When resource object (or its relations) is empty{}object then it converted tonullvalue before perform request to the server.
Property collections contains all format settings for collections. Available the next settings for collections:
embeddedOptional- by defaultfalse, means that server side should add_embeddedproperty when returns empty resource collection like that:
{
"_embedded" : {
"orders" : [
]
}
}when value is set to true then it is not required to add _embedded property when returns empty resource collection.
Some hateoas-client features can change their behaviours depends on this param. For example, Logging disable all warning messages when isProduction is true.
isProduction- need set totruewhen running in production environment,falseby default.
Let to change default page params that use when a page request not pass page params.
size- change default page size, current value you can see here.page- change default page number, current value you can see here.
The library supports caching GET request response values.
By default, the cache enabled.
To enable cache pass cache.enabled = true to library configuration. Also, you can manage the cache expired time with cache.lifeTime param.
Also, you can specify cache mode that allows to enable cache always except case when explicitly passed useCache = false or vice versa use only when passed useCache = true.
More about the cache configuration see here.
Also, methods with options types GetOption or PagedGetOption has additional param useCache that allows to manage the cache.
By default useCache has true value, but when the cache disabled then it param ignored.
If you need to evict all cache data you can use HateoasResourceService#evictResourcesCache method for that:
...
constructor(private resourceService: HateoasResourceService) {}
public someMethod(): void {
this.resourceService.evictResourcesCache();
}
...After each successful GET request to get a resource or resource collection or paginated resource collection, response placed in the cache map.
The cache map key is CacheKey object that holds full request url and all request options.
The cache map value is raw hal-json response.
When perform the same GET request twice (to get resource or resource collection or paginated resource collection). First request hits the server to get the result, second request does not hit the server and result got from the cache.
When perform POST, PATCH, PUT, DELETE requests for some resource than all saved cache values for this resource will be evicted.
You can know when resource result got from the server or got from the cache enable the library verboseLogs param. See more about logging here.
To debug library use library logging.
To enable logging set logs.verboseLogs to true value see more here.
There are several logging stages:
BEGIN- first stage, when method invokedCHECK_PARAMS- logs errors after validation input paramsPREPARE_URL- logs parts of generated request URL fromrootUrl/proxyUrl,resource nameand passed optionsPREPARE_PARAMS- logs applied default request paramsINIT_RESOURCE- logs when on initializing resource from a response errors occursRESOLVE_VALUES- logs stage when resolving resource relations converting related resource object to a link urlCACHE_PUT- logs a value saved to the cacheCACHE_GET- logs a received value from the cacheCACHE_EVICT- logs an evicted value from the cacheHTTP_REQUEST- logs HTTP request paramsHTTP_RESPONSE- logs raw HTTP response dataEND- last stage, when method invoke was successful
With logging, you can find out was a value fetched from the cache or the server, how resource relationships resolved etc.
Logs can appear in the browser's console with a warning level. These messages can help you to understand what is problem and how you need to fix it.
It, not good practice to show these messages in production therefore warning level logs to disable when hateoas-client used in production mode.
See more configuration section how to set production mode.
This section describes public classes available to use in client apps.
Contains common options for all requests, it is allowing to add Angular HttpClient options to resource request.
Uses directly as option type in methods that create, change or delete resource.
With RequestOption you can change response type to HttpResponse passing observe: 'response', by default used observe: body.
Also, you can pass params that will be added to the HTTP request as HTTP params and other Angular HttpClient options.
export interface RequestOption {
params?: [paramName: string]: Resource | string | number | boolean;
headers?: HttpHeaders | {
[header: string]: string | string[];
};
observe?: 'body' | 'response';
reportProgress?: boolean;
withCredentials?: boolean;
}
paramsiskey: valuevalues that will be added to the HTTP request as HTTP paramsobserveis response type paramheadersis http headers
Uses as option type in methods that retrieve resource or resource collection from the server.
GetOption extends RequestOption and adds additional params to GET request.
export interface GetOption extends RequestOption {
sort?: Sort;
useCache?: boolean;
}
sortis Sort object with response sort optionsuseCacheis property allows to disable the cache for current request
Uses as option type in methods that retrieve paginated resource collection from the server.
PagedGetOption extends GetOption and adds the page params.
export interface PagedGetOption extends GetOption {
pageParams?: PageParam;
}
export interface PageParam {
page?: number;
size?: number;
}
pageis page number paramsizeis page size param
Uses as request body type in methods that create or change resource.
RequestBody is an interface that describes the next options:
export interface RequestBody<T> {
body: T;
valuesOption?: ValuesOption;
}
export interface ValuesOption {
include: Include;
}
export enum Include {
NULL_VALUES = 'NULL_VALUES',
RESOURCES_REL_AS_OBJECTS = 'RESOURCES_REL_AS_OBJECTS'
}
bodyis a request body objectvaluesOptionare additional options that allows manipulation body object values
For example, passed body as {body: {name: 'Name', age: null}} by default any properties that have null values will be ignored and will not pass.
To pass null values, need to pass valuesOption: {include: Include.NULL_VALUES}.
When body object has
undefinedproperty then it will be ignored even you passInclude.NULL_VALUES.
By default, the lib replaces all related Resource's with related Resource self link before perform request to the server.
If you want to disable this behavior, you can pass the additional option valuesOption: {include: Include.RESOURCES_REAL_AS_OBJECTS} then the lib will pass related Resources as a simple JSON object.
For example, you want to create some Resource like this:
{
"name": "Test",
"status": "NEW",
"author": {
"id": 1,
"username": "authorName",
"email": "[email protected]",
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/users/1"
},
"author": {
"href": "http://localhost:8080/api/v1/users/1"
}
}
}
}By default, the lib will convert this JSON to the next view (that fit Spring Data Rest notation):
{
"name": "Test",
"status": "NEW",
"author": "http://localhost:8080/api/v1/users/1"
}Using the RESOURCES_REL_AS_OBJECTS option, will pass author as JSON object:
{
"name": "Test",
"status": "NEW",
"author": {
"id": 1,
"username": "authorName",
"email": "[email protected]",
"_links": {
"self": {
"href": "http://localhost:8080/api/v1/users/1"
},
"author": {
"href": "http://localhost:8080/api/v1/users/1"
}
}
}
}Uses as param type in methods that applied GetOption, PagedGetOption options.
Sort params are key: value object where key is a property name to sort and value is sort order.
export interface Sort {
[propertyToSort: string]: SortOrder;
}
export type SortOrder = 'DESC' | 'ASC';
Uses as option type in PagedResourceCollection in customPage method.
SortedPageParam is an interface with page and sort params.
export interface SortedPageParam {
pageParams?: PageParam;
sort?: Sort;
}
export interface PageParam {
page?: number;
size?: number;
}
pageParamsis page number and page size paramssortis Sort object with response sort options