@lagoshny/ngx-hateoas-client
Version:
This client used to develop `Angular 12+` applications working with RESTfulll server API with HAL/JSON response type (supports server implementation by Spring HATEOAS)
1,480 lines (1,111 loc) • 129 kB
Markdown
# NgxHateoasClient
<a href="https://www.npmjs.com/package/@lagoshny/ngx-hateoas-client/v/latest">
<img src="https://img.shields.io/npm/v/@lagoshny/ngx-hateoas-client/latest?label=npm" alt="Last released npm version" />
</a>
<a href="https://github.com/lagoshny/ngx-hateoas-client/actions?query=workflow%3ABuild">
<img src="https://github.com/lagoshny/ngx-hateoas-client/actions/workflows/nodejs.yml/badge.svg?branch=master" alt="Pipeline info" />
</a>
<a href="https://github.com/lagoshny/ngx-hateoas-client/issues">
<img src="https://img.shields.io/github/issues/lagoshny/ngx-hateoas-client" alt="Total open issues" />
</a>
<a href="https://www.npmjs.com/package/@lagoshny/ngx-hateoas-client">
<img src="https://img.shields.io/npm/dt/@lagoshny/ngx-hateoas-client" alt="Total downloads by npm" />
</a>
<a href="https://mit-license.org/">
<img src="https://img.shields.io/npm/l/@lagoshny/ngx-hateoas-client" alt="License info" />
</a>
<br />
<br />
## ⭐Compatible with Angular 12.x.x - 19.x.x versions that uses `Ivy compilation`.
### ⚠ If you use old `View Engine` compilation or Angular 6.x.x - 11.x.x you need to use [2.x.x](https://github.com/lagoshny/ngx-hateoas-client/tree/ng-ve) lib version.
>
>See more about it [here](https://github.com/lagoshny/ngx-hateoas-client/blob/master/CHANGELOG.md#303-2021-12-23).
### 💡 New versioning policy.
- Versions that work with old `View Engine` compilation [`2.0.0`-`2.x.x`].
- Versions that work with new `Ivy` compilation [`3.0.0`-`x.x.x`].
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](https://martinfowler.com/articles/richardsonMaturityModel.html)
and the server provides [HAL/JSON](http://stateless.co/hal_specification.html) response type.
This client compatible with Java server-side applications based on [Spring HATEOAS](https://spring.io/projects/spring-hateoas) or [Spring Data REST](https://docs.spring.io/spring-data/rest/docs/current/reference/html/#reference).
>This client is a continuation of the [@lagoshny/ngx-hal-client](https://github.com/lagoshny/ngx-hal-client).
You can find out about the motivation to create a new client [here](https://github.com/lagoshny/ngx-hateoas-client/blob/master/migration-guide.md#Motivation).
To migrate from `@lagoshny/ngx-hal-client` to this client you can use the [migration guide](https://github.com/lagoshny/ngx-hateoas-client/blob/master/migration-guide.md#Motivation).
You can found examples of usage this client with [task-manager-front](https://github.com/lagoshny/task-manager-front) application that uses server-side [task-manager-back](https://github.com/lagoshny/task-manager-back) application.
## Contents
1. [Changelog](#Changelog)
2. [Getting started](#Getting-started)
- [Installation](#Installation)
- [Configuration](#Configuration)
- [Common Resource URL](#using-common-url-for-retrieve-resources)
- [Multiple Resource URLs](#using-multiple-urls-to-retrieve-resources)
- [Usage](#Usage)
- [Define resource classes](#Define-resource-classes)
- [Built-in HateoasResourceService](#built-in-hateoasresourceservice)
- [Create custom Resource service](#Create-custom-Resource-service)
3. [Testing](#Testing)
4. [Resource types](#Resource-types)
- [Decorators](#decorators)
- [@HateoasResource](#hateoasresource)
- [resourceName](#resourcename)
- [options](#options)
- [@HateoasEmbeddedResource](#hateoasembeddedresource)
- [@HateoasProjection](#hateoasprojection)
- [@ProjectionRel](#projectionrel)
- [BaseResource](#BaseResource)
- [HasRelation](#HasRelation)
- [GetRelation](#GetRelation)
- [GetRelatedCollection](#GetRelatedCollection)
- [GetRelatedPage](#GetRelatedPage)
- [PostRelation](#PostRelation)
- [PatchRelation](#PatchRelation)
- [PutRelation](#PutRelation)
- [Resource](#Resource)
- [AddCollectionRelation](#AddCollectionRelation)
- [BindRelation](#BindRelation)
- [UnbindRelation](#UnbindRelation)
- [UnbindCollectionRelation](#UnbindCollectionRelation)
- [DeleteRelation](#DeleteRelation)
- [EmbeddedResource](#EmbeddedResource)
- [ResourceCollection](#ResourceCollection)
- [PagedResourceCollection](#PagedResourceCollection)
- [Subtypes support](#Subtypes-support)
- [Resource projection support](#resource-projection-support)
- [ProjectionRelType](#projectionreltype)
5. [Resource service](#Resource-service)
- [GetResource](#GetResource)
- [GetCollection](#GetCollection)
- [GetPage](#GetPage)
- [CreateResource](#CreateResource)
- [UpdateResource](#UpdateResource)
- [UpdateResourceById](#UpdateResourceById)
- [PatchResource](#PatchResource)
- [PatchResourceById](#PatchResourceById)
- [DeleteResource](#DeleteResource)
- [DeleteResourceById](#DeleteResourceById)
- [SearchResource](#SearchResource)
- [SearchCollection](#SearchCollection)
- [SearchPage](#SearchPage)
- [CustomQuery](#CustomQuery)
- [CustomSearchQuery](#CustomSearchQuery)
6. [Settings](#settings)
- [Configuration params](#Configuration-params)
- [Http params](#http-params)
- [UseTypes](#usetypes-params)
- [TypesFormat](#typesformat)
- [HALFormat](#halformat)
- [Cache support](#cache-support)
- [Evict all cache](#evict-all-cache-data)
- [Logging](#Logging)
7. [Public classes](#Public-classes)
- [RequestOption](#RequestOption)
- [GetOption](#GetOption)
- [PagedGetOption](#PagedGetOption)
- [RequestBody](#RequestBody)
- [NULL_VALUES](#nullvalues)
- [REL_RESOURCES_AS_OBJECTS](#relresourcesasobjects)
- [Sort](#Sort)
- [SortedPageParam](#SortedPageParam)
## Changelog
[Learn about the latest improvements](https://github.com/lagoshny/ngx-hateoas-client/blob/master/CHANGELOG.md).
## Getting started
### Installation
To install the latest version use command:
```
npm i @lagoshny/ngx-hateoas-client@latest --save
```
### Configuration
> Important! Starts from 3.1.0 version you need manually import HttpClientModule to provide HttpClient service that required fot this lib.
> Why it was change you can see this [Angular issue](https://github.com/angular/angular/issues/20575).
Before start, need to configure `NgxHateoasClientModule` and pass configuration through `NgxHateoasClientConfigurationService`.
1) `NgxHateoasClientModule` configuration:
```ts
import { NgxHateoasClientModule } from '@lagoshny/ngx-hateoas-client';
...
@NgModule({
...
imports: [
HttpClientModule,
...
NgxHateoasClientModule.forRoot()
...
]
...
})
export class AppModule {
...
}
```
2) In constructor app root module inject `NgxHateoasClientConfigurationService` and pass a configuration:
Minimal configuration look like this:
#### Using common URL to retrieve `Resources`
```ts
import { ..., NgxHateoasClientConfigurationService } from '@lagoshny/ngx-hateoas-client';
...
export class AppModule {
constructor(hateoasConfig: NgxHateoasClientConfigurationService) {
hateoasConfig.configure({
http: {
rootUrl: 'http://localhost:8080/api/v1'
}
});
}
}
```
#### Using multiple URLs to retrieve `Resources`
```ts
import { ..., NgxHateoasClientConfigurationService } from '@lagoshny/ngx-hateoas-client';
...
export class AppModule {
constructor(hateoasConfig: NgxHateoasClientConfigurationService) {
hateoasConfig.configure({
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](#options) to specify it.
<br>
<br>
>See more about other configuration params [here](#configuration-params).
### Usage
### Define resource classes
To represent model class as a resource model extend model class by `Resource` class.
Besides you need to decorate the class with [@HateoasResource](#hateoasresource) decorator that will register your resource class with passed `resourceName` in `hateoas-client`.
Suppose you have some `Product` model class:
```ts
export class Product {
public name: string;
public cost: number;
}
```
After making it as a `Resource` it will look like this:
```ts
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](#resource-projection-support).
>Also, you can extend model classes with the `EmbeddedResource` class and decorate with [@HateasEmbeddedResource](#hateoasembeddedresource) decorator when the model class used as an [embeddable](https://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html) entity.
You can read more about `EmbeddedResource` [here](#embeddedresource).
It is recommended also to declare the `Product` resource class (others `Resources` and `EmbeddedResources` too) in the `hateoas-client` configuration:
```ts
...
hateoasConfig.configure({
...
useTypes: {
resources: [Product]
}
}
...
```
>See more about `useTypes` in the configuration section [here](#usetypes-params).
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](#built-in-hateoasresourceservice) or a create [custom resource service](#create-custom-resource-service) for concrete resource class.
### Built-in HateoasResourceService
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.
```ts
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](#resource) class and decorated with [@HateoasResource](#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](#resource-service).
>`HateoasResourceService` is a universal service that can work with several `Resources` at 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 once `HateoasResourceService` and 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](#create-custom-resource-service).
### Create custom Resource service
To create a custom resource service create a new service and extends it with `HateoasResourceOperation` and pass `resourceType` to the parent constructor.
```ts
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](#resource-service) as `HateoasResourceService` without `resourceType` as the first param (because you pass `resourceType` with service constructor).
## Testing
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):
- `NgxHateoasClientConfigurationService`
- `HateoasResourceService`
Below you can find simple lib test examples:
Suppose you have the `User` and some `UserService` like that:
```ts
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.
### Using TestBed
If you prefer to use standard `TestBed` for testing, you can do that in the following way:
````ts
import {
HateoasResourceService,
NgxHateoasClientModule,
PagedResourceCollection,
ResourceCollection
} from '@lagoshny/ngx-hateoas-client';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs';
describe('UserServiceTest', () => {
let hateoasResourceServiceSpy;
beforeEach(() => {
hateoasResourceServiceSpy = {
createResource: jasmine.createSpy('createResource'),
searchPage: jasmine.createSpy('searchPage')
};
TestBed.configureTestingModule({
imports: [
NgxHateoasClientModule.forRoot()
],
providers: [
UserService,
{provide: HateoasResourceService, useValue: hateoasResourceServiceSpy}
]
});
});
it('should init service', () => {
const userService = TestBed.inject(UserService);
expect(userService).toBeTruthy();
});
it('should create new user', waitForAsync(() => {
const userService = TestBed.inject(UserService);
const newUser = new User();
newUser.id = '1';
hateoasResourceServiceSpy.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 = TestBed.inject(UserService);
const returnedUser = new User();
returnedUser.id = '1';
const resourceCollection = new ResourceCollection<User>();
resourceCollection.resources = [returnedUser];
hateoasResourceServiceSpy.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');
});
}));
});
````
### Using Spectator
If you prefer to use [@ngneat/spectator](https://www.npmjs.com/package/@ngneat/spectator) for testing, you can do that in the following way:
```ts
import {
HateoasResourceService,
NgxHateoasClientModule,
PagedResourceCollection,
ResourceCollection
} from '@lagoshny/ngx-hateoas-client';
import { waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
describe('UserServiceTest', () => {
let spectator: SpectatorService<UserService>;
const createService = createServiceFactory({
imports: [NgxHateoasClientModule.forRoot()],
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');
});
}));
});
```
## Resource types
There are several types of resources: the main resource type is [Resource](#resource) represents the server-side entity model class.
If the server-side model has Embeddable entity type then use [EmbeddedResource](#embeddedresource) type instead [Resource](#resource) type.
Both [Resource](#resource) and [EmbeddedResource](#embeddedresource) have some the same methods therefore they have common parent [BaseResource](#baseresource) class implements these methods.
Also, you can create `Resource` class to represent resource projection, see more about resource projection [here](#resource-projection-support).
>Each `Resource`/`EmbeddedResource` has decorators [@HateoasResource](#hateoasresource)/[@HateoasEmbeddedResource](#hateoasembeddedresource) respectively. They're used to register info about your resources in `hateoas-client`. For example, `resourceName` that used to build resource requests.
To work with resource collections uses [ResourceCollection](#resourcecollection) type its holds an array of the resources.
When you have a paged collection of resources result use an extension of [ResourceCollection](#resourcecollection) is [PagedResourceCollection](#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](#subtypes-support).
## Decorators
### @HateoasResource
`@HateoasResource` decorator use to register your `Resource` classes in `hateoas-client` with passed `resourceName` as decorator's param.
- `resourceName`: `string` should be equals to the server-side resource name that uses to represent self resource link.
- `options`: `ResourceOption` additional resource options. Find more about these options [here](#options).
#### resourceName
Using to specify resource name.
For example, you need to work with `Shop`resource:
```ts
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 `Resource` classes with this decorator otherwise you will get an error when performing resource request
#### options
`ResourceOption` contains properties:
```ts
{
routeName: string; // name of Resource route
}
```
If you want to use special `URL` to get `Resource` use `options#routeName` param:
```ts
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](#using-multiple-urls-to-retrieve-resources) section.
> If you not specify `routerName` it will be used default router name as `defaultRouter`.
### @HateoasEmbeddedResource
`@HateoasEmbeddedResource` decorator use to register your `EmbeddedResource` classes in `hateoas-client` with passed `relationNames` as decorator's param.
- `relationNames` is an array of the names where each is the name of relation with which `EmbeddedResource` using in `Resource` class.
For example, you have `Client` resource that using `Address` embedded resource:
```ts
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 `EmbeddedResource` classes with this decorator if wou want get concrete embedded resource class (in this example `Address` class). Otherwise you will get a warning about creating an embedded resource when parsing server's answer with the default `EmbeddedResource` class when performing resource request that using an embedded resource.
### @HateoasProjection
`@HateoasProjection` decorator use to register your projection resource classes in `hateoas-client` with passed `resourceType` and `projectionName` as decorator's params.
- `resourceType` equals to resource type that use this resource projection.
- `projectionName` should be equals to the server-side resource projection name.
For example, you have `Shop` resource projection with the name `shopProjection`:
```ts
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](#projectionreltype) type and mark these relations with [@ProjectionRel](#projectionrel) decorator with a relation resource type.
- [ProjectionRelType](#projectionreltype) will hide `Resource`/`EmbeddedResource` methods for projection relation that will lead clear projection relation interface.
- [@ProjectionRel](#projectionrel) decorator will be used to create relation with concrete resource type when parsing server-side answer.
See more about resource projection support [here](#resource-projection-support).
#### @ProjectionRel
`@ProjectionRel` decorator use to register projection relation resource type in `hateoas-client` with passed `relationType` as decorator's param.
- `relationType` equals 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:
```ts
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 knowing`hateoas-client` which relation resource type needs to use when creating a resource from the serv-side answer.
> Here used `ProjectionRelType` type as a wrapper for `Cart` resource class, to hide `Resource`/`EmbeddedResource` methods in projection relation.
> See more [here](#projectionreltype) about this type.
### Resource presets
Examples of usage resource relation methods rely on presets.
- Resource classes are
```ts
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:
```ts
hateoasConfig.configure({
http: {
rootUrl: 'http://localhost:8080/api/v1'
},
useTypes: {
resources: [Cart, Shop, Product, Client, PhysicalClient, JuridicalClient]
}
})
```
- Suppose we have existed resources:
```json
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"
}
}
}
```
## BaseResource
Parent class for [Resource](#resource) and [EmbeddedResource](#embeddedresource) classes.
Contains common resource methods to work with resource relations through resource links (see below).
### HasRelation
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` - `true` if resource has that relation, `false` otherwise.
### GetRelation
Getting resource relation object by relation name.
This method takes [GetOption](#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](#getoption) additional options applied to the request.
- `return value` - [Resource](#resource) with type `T`.
- `throws error` - when required params are not valid or link not found by relation name or returned value is not [Resource](#resource).
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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:
```ts
// 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
});
```
### GetRelatedCollection
Getting related resource collection by relation name.
This method takes [GetOption](#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](#getoption) additional options applied to the request.
- `return value` - [ResourceCollection](#resourcecollection) collection of resources with type `T`.
- `throws error` - when required params are not valid or link not found by relation name or returned value is not [ResourceCollection](#resourcecollection).
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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:
```ts
// 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
});
```
### GetRelatedPage
Getting related resource collection with pagination by relation name.
This method takes [PagedGetOption](#pagedgetoption) parameter with it you can pass `projection` param (see below).
>If do not pass `pageParams` with `PagedGetOption` then will be used [default page options](#default-page-values).
>Also, you can set up own default page params through [configuration](#pagination-params).
Method signature:
```
getRelatedPage<T extends PagedResourceCollection<BaseResource>>(relationName: string, options?: PagedGetOption): Observable<T>;
```
- `relationName` - resource relation name used to get request URL.
- `options` - [PagedGetOption](#pagedgetoption) additional options applied to the request, if not passed `pageParams` then used [default page params](#default-page-values).
- `return value` - [PagedResourceCollection](#pagedresourcecollection) paged collection of resources with type `T`.
- `throws error` - when required params are not valid or link not found by relation name or returned value is not [PagedResourceCollection](#pagedresourcecollection).
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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:
```ts
// 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();
*/
});
```
### PostRelation
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](#requestbody) contains request body and additional body options.
- `options` - [RequestOption](#requestoption) additional options applied to the request.
- `return value` - by default `raw response data` or Angular `HttpResponse` when `options` param has a `observe: 'response'` value.
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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:
```ts
// 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
});
```
### PatchRelation
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](#requestbody) contains request body and additional body options.
- `options` - [RequestOption](#requestoption) additional options applied to the request.
- `return value` - by default `raw response data` or Angular `HttpResponse` when `options` param has a `observe: 'response'` value.
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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:
```ts
// 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
});
```
### PutRelation
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](#requestbody) contains request body and additional body options.
- `options` - [RequestOption](#requestoption) additional options applied to the request.
- `return value` - by default `raw response data` or Angular `HttpResponse` when `options` param has a `observe: 'response'` value.
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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:
```ts
// 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
});
```
## Resource
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](#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](#embeddedresource) classes are `@Embeddable` entities.
`Resource` class extend [BaseResource](#baseresource) with additional resource relations methods that used only with `Resource` type.
>Each `Resource` class should be declared with [@HateoasResource](#hateoasresource) decorator.
### AddCollectionRelation
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](#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` - Angular `HttpResponse` result.
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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
});
```
### BindRelation
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](#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` - Angular `HttpResponse` result.
##### Examples of usage ([given the presets](#resource-presets)):
With single resource relation:
```ts
// 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:
```ts
// 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
});
```
### UnbindRelation
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](#unbindCollectionRelation) method.
> To delete one resource from resource collection use [deleteRelation](#deleterelation) method.
Method signature:
```
unbindRelation<T extends Resource>(relationName: string): Observable<HttpResponse<any>>;
```
- `relationName` - resource relation name to unbind.
- `return value` - Angular `HttpResponse` result.
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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
});
```
### UnbindCollectionRelation
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](#unbindrelation) or [deleteRelation](#deleterelation) methods.
> To delete one resource from collection use [deleteRelation](#deleterelation) method.
Method signature:
```
unbindCollectionRelation<T extends Resource>(relationName: string): Observable<HttpResponse<any>>;
```
- `relationName` - resource relation name used to get request URL.
- `return value` - Angular `HttpResponse` result.
##### Examples of usage ([given the presets](#resource-presets)):
```ts
/*
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
});
```
### DeleteRelation
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](#unbindrelation) method.
>To delete all resource relations from collection use [unbindCollectionRelation](#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` - Angular `HttpResponse` result.
##### Examples of usage ([given the presets](#resource-presets)):
```ts
// 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
});
```
## EmbeddedResource
This resource type uses when a server-side entity is [@Embeddable](https://docs.jboss.org/hibernate/orm/5.0/userguide/html_single/chapters/domain/embeddables.html).
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](#baseresource) methods.
>Each `EmbeddedResource` class should be declared with [@HateoasEmbeddedResource](#hateoasembeddedresource) decorator.
## ResourceCollection
This resource type represents collection of resources.
You can get this type as result [GetRelatedCollection](#getrelatedcollection), [GetResourceCollection](#getresourcecollection) or perform [CustomQuery](#customquery)/[CustomSearchQuery](#customsearchquery) with passed return type as `ResourceCollection`.
Resource collection holds resources in the public property with the name `resources`.
## PagedResourceCollection
This resource type represents paged collection of resources.
You can get this type as result [GetRelatedPage](#getrelatedpage), [GetPage](#getpage) or perform [CustomQuery](#customquery)/[CustomSearchQuery](#customsearchquery) with passed return type as PagedResourceCollection.
PagedResourceCollection extends [ResourceCollection](#resourcecollection) type and adds methods to work with a page.
### Default page values
When you do not pass `page` or `size` params in methods with [PagedGetOption](#pagedgetoption) then used [default page values](#default-page-values).
>Also, you can set up own default page params through [configuration](#pagination-params).
### HasFirst
Checks that `PagedResourceCollection` has the link to get the first-page result.
Method signature:
```
hasFirst(): boolean;
```
- `return value` - `true` when the link to get the first page exists, `false` otherwise.
### HasLast
Checks that `PagedResourceCollection` has the link to get the last page result.
Method signature:
```
hasLast(): boolean;
```
- `return value` - `true` when the link to get the last page exists, `false` otherwise.
### HasNext
Checks that `PagedResourceCollection` has the link to get the next page result.
Method signature:
```
hasNext(): boolean;
```
- `return value` - `true` when the link to get the next page exists, `false` otherwise.
### HasPrev
Checks that `PagedResourceCollection` has the link to get the previous page result.
Method signature:
```
hasPrev(): boolean;
```
- `return value` - `true` when the link to get the prev page exists, `false` otherwise.
### First
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](#cache-params)).
- `return value` - [PagedResourceCollection](#pagedresourcecollection) with resource types `T`.
- `throws error` - when the link to get the first-page result is not exist.
##### Exa