UNPKG

@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
# 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>&nbsp; <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>&nbsp; <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>&nbsp; <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>&nbsp; <a href="https://mit-license.org/"> <img src="https://img.shields.io/npm/l/@lagoshny/ngx-hateoas-client" alt="License info" /> </a>&nbsp; <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