{"id":43226589,"url":"https://github.com/granito-source/ngx-hal-client","last_synced_at":"2026-02-01T09:38:23.944Z","repository":{"id":196253398,"uuid":"695618746","full_name":"granito-source/ngx-hal-client","owner":"granito-source","description":"Angular HAL Client","archived":false,"fork":false,"pushed_at":"2026-01-17T14:50:31.000Z","size":1536,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-18T00:39:11.055Z","etag":null,"topics":["angular","client","hal"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/granito-source.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-09-23T18:05:33.000Z","updated_at":"2026-01-17T14:50:35.000Z","dependencies_parsed_at":"2023-09-23T22:33:37.676Z","dependency_job_id":"4cc5835c-a0e6-4fa4-8241-4e7b10699cb2","html_url":"https://github.com/granito-source/ngx-hal-client","commit_stats":null,"previous_names":["granito-source/ngx-hal-client"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/granito-source/ngx-hal-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/granito-source%2Fngx-hal-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/granito-source%2Fngx-hal-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/granito-source%2Fngx-hal-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/granito-source%2Fngx-hal-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/granito-source","download_url":"https://codeload.github.com/granito-source/ngx-hal-client/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/granito-source%2Fngx-hal-client/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28975261,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T08:16:14.655Z","status":"ssl_error","status_checked_at":"2026-02-01T08:06:51.373Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","client","hal"],"created_at":"2026-02-01T09:38:19.839Z","updated_at":"2026-02-01T09:38:23.934Z","avatar_url":"https://github.com/granito-source.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build](https://github.com/granito-source/ngx-hal-client/actions/workflows/build.yaml/badge.svg)](https://github.com/granito-source/ngx-hal-client/actions/workflows/build.yaml)\n[![Latest Version](https://img.shields.io/npm/v/%40granito%2Fngx-hal-client.svg)](https://npm.im/@granito/ngx-hal-client)\n[![License](https://img.shields.io/github/license/granito-source/ngx-hal-client?color=blue)](https://github.com/granito-source/ngx-hal-client/blob/main/LICENSE)\n\nThis is a\n[Hypertext Application Language](https://en.wikipedia.org/wiki/Hypertext_Application_Language)\n(HAL) client to be used in [Angular](https://angular.io/) projects.\n\n## Installation\n\nUsing [npm](https://npmjs.org/):\n\n```shell\n$ npm install @granito/ngx-hal-client --save\n```\n\n## Building from source\n\n```shell\n$ git clone https://github.com/granito-source/ngx-hal-client.git\n$ cd ngx-hal-client\n$ npm install\n$ npm run build\n```\n\n### Running the tests\n\n```shell\n$ npm tests\n```\n\n## Basic usage\n\nFor illustrative purposes, let's assume that your Angular application\nneeds to work with the following HAL API.\n\nThe root entry point is `/api/v1`. When one executes `GET` on this URI,\nthe returned object looks like\n\n```json\n{\n    \"apiVersion\": \"1.7.21\",\n    \"_links\": {\n        \"self\": {\n            \"href\": \"/api/v1\"\n        },\n        \"messages\": {\n            \"href\": \"/api/v1/messages\"\n        }\n    }\n}\n```\n\nIn other words, it exposes the implementation version and declares one\nlink to work with a collection of messages. When one executes `GET` on\n`/api/v1/messages` the API returns a collection page like this\n\n```json\n{\n    \"start\": 0,\n    \"_links\": {\n        \"self\": {\n            \"href\": \"/api/v1/messages?start=0\"\n        },\n        \"next\": {\n            \"href\": \"/api/v1/messages?start=2\"\n        }\n    },\n    \"_embedded\": {\n        \"selected\" : {\n            \"id\": 0,\n            \"text\": \"Then what do they call it?\",\n            \"_links\": {\n                \"self\": {\n                    \"href\": \"/api/v1/messages/0\"\n                }\n            }\n        },\n        \"items\": [\n            {\n                \"id\": 0,\n                \"text\": \"Then what do they call it?\",\n                \"_links\": {\n                    \"self\": {\n                        \"href\": \"/api/v1/messages/0\"\n                    },\n                    \"next\": {\n                        \"href\": \"/api/v1/messages/1\"\n                    }\n                }\n            },\n            {\n                \"id\": 1,\n                \"text\": \"They call it Royale with Cheese.\",\n                \"_links\": {\n                    \"self\": {\n                        \"href\": \"/api/v1/messages/1\"\n                    },\n                    \"prev\": {\n                        \"href\": \"/api/v1/messages/0\"\n                    }\n                }\n            }\n        ]\n    }\n}\n```\n\n### Configure HAL Client\n\nHAL Client uses Angular HTTP Client. You have to configure HTTP Client\nusing dependency injection before HAL Client can be used. The modern\nway of doing it using `providers` in `app.config.ts` is shown below.\nThe HAL Client is configured by calling `provideHalRoot()` function\nwith the URI of the root entry point.\n\n```ts\nimport { provideHttpClient } from '@angular/common/http';\nimport { provideHalRoot } from \"@granito/ngx-hal-client\";\n\nexport const appConfig: ApplicationConfig = {\n    providers: [\n        provideHttpClient(),\n        provideHalRoot('/api/v1')\n    ]\n};\n```\n\n### Define resources\n\nThen you need to have some resources defined, e.g. the root resource for\nyour API hierarchy. If your root resource does not have any properties,\nyou can just use `Resource`. Otherwise, like in our sample API, extend\n`Resource` class as needed.\n\n```ts\nimport { Resource } from '@granito/ngx-hal-client';\n\nexport class ApiRoot extends Resource {\n    declare apiVersion: string;\n}\n```\n\nand\n\n```ts\nimport { Resource } from '@granito/ngx-hal-client';\n\nexport class Message extends Resource {\n    declare id: number;\n\n    declare text: string;\n}\n```\n\nCollections are represented by `Collection` resource.\n\n### Read the root resource\n\nHAL Client uses `Accessor` object to give access to HAL resources.\nIn order to access the root entry point of the API, you need to get\nits accessor first using `HAL_ROOT` injection token and then read\nthe API root resource. Normally you would keep the root entry point\nof the API as your application's state, e.g. in a `ReplaySubject`.\n\n```ts\nimport { Inject, Injectable } from '@angular/core';\nimport { Accessor, HAL_ROOT } from '@granito/ngx-hal-client';\nimport { Observable, ReplaySubject } from 'rxjs';\nimport { ApiRoot } from './api-root';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiRootService {\n    private readonly apiRoot$ = new ReplaySubject\u003cApiRoot\u003e(1);\n\n    get apiRoot(): Observable\u003cApiRoot\u003e {\n        return this.apiRoot$.asObservable();\n    }\n\n    constructor(@Inject(HAL_ROOT) halRoot: Accessor) {\n        this.halRoot.read(ApiRoot)\n            .subscribe(api =\u003e this.apiRoot$.next(api));\n    }\n}\n```\n\n### Following links\n\nOnce you have at least one resource instance, e.g. the API root from\nthe example above, you can follow links available in the resource to\nget accessors for the linked resources.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class MessageService {\n    private messages$: Observable\u003cAccessor\u003e;\n\n    constructor(apiRootService: ApiRootService) {\n        this.messages$ = apiRootService.apiRoot.pipe(\n            follow('messages')\n        );\n    }\n}\n```\n\nIn the example above, `messages$` observable will emit either the\naccessor to the collection of messages or undefined if the API root\ndoes not have `messages` link.\n\n### CRUD operations\n\n`Accessor` and `Resource` objects allow you to execute CRUD operations\nusing HAL API. Some operations are defined on both objects and work\nidentically, some are defined only on `Resource`, and some are available\nin both but have somewhat different syntax and semantic.\n\n#### Read collection\n\nNow that you have an accessor for the collection of messages, you can\nread the collection and access its elements.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class MessageService {\n    private messages$: Observable\u003cAccessor\u003e;\n\n    // ...\n\n    readFirst(): Observable\u003cMessage\u003e {\n        return this.messages$.pipe(\n            take(1),\n            readCollection(Message),\n            map(collection =\u003e collection.values[0])\n        );\n    }\n}\n```\n\nThe example above shows how to access the first message in the collection\nif it exists.\n\n#### Read resource\n\nMessages are resources, and they may have their own links. You can read resources referenced by these links as shown below.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class MessageService {\n    private current$: Observable\u003cMessage\u003e;\n\n    // ...\n\n    readNext(): Observable\u003cMessage\u003e {\n        return this.current$.pipe(\n            take(1),\n            follow('next'),\n            read(Message)\n        );\n    }\n}\n```\n\nThe example shows how to access the next message in the thread if\nit exists.\n\n#### Refresh\n\nThe library provides `refresh()` RxJS operator to execute a read\noperation on the resource using its `self` link. For example, here\nis how you can refresh the API root in `ApiRootService`.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class ApiRootService {\n    // ...\n\n    refresh(): void {\n        this.apiRoot$.pipe(\n            take(1),\n            refresh()\n        ).subscribe(api =\u003e this.apiRoot$.next(api));\n    }\n}\n```\n\n#### Create resource\n\nTo create a new message, you can use `create()` operator.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class MessageService {\n    private messages$: Observable\u003cAccessor\u003e;\n\n    // ...\n\n    post(message: { text: string; }): Observable\u003cAccessor\u003e {\n        return this.messages$.pipe(\n            take(1),\n            create(message)\n        );\n    }\n}\n```\n\nTwo things are worth mentioning here. First, the object passed to\n`create()` operator does not have to be a `Resource`. Second, if the\n`POST` operation returns the URI for the newly created resource\nin the `Location` header, then the observable will emit an accessor\nfor this resource. You can use it to read the resource immediately after\nit is created, e.g. by using `read()` operator.\n\n#### Update resource\n\n`Resource` instances can be updated in the API by using `update()`\noperator. Because the resources are normally frozen, you have to use\n`mutate()` operator to create a new instance and update its properties\nas needed.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class MessageService {\n    private current$: Observable\u003cMessage\u003e;\n\n    // ...\n\n    edit(text: string): Observable\u003cMessage\u003e {\n        return this.current$.pipe(\n            take(1),\n            mutate(message =\u003e message.text = text),\n            update()\n        );\n    }\n}\n```\n\nOn successful completion the observable will re-emit the mutated resource.\n\n#### Delete resource\n\nAnd finally, resources and collections can be deleted using `del()`\noperator.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class MessageService {\n    private current$: Observable\u003cMessage\u003e;\n\n    // ...\n\n    deleteCurrent(): Observable\u003cvoid\u003e {\n        return this.current$.pipe(\n            take(1),\n            del()\n        );\n    }\n}\n```\n\n### Access embedded resources\n\n`Resource` offers two methods to access embedded HAL resources. In order\nto access embedded arrays, you can use `getArray()` method.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class MessageService {\n    // ...\n\n    array(messages: Collection\u003cMessage\u003e): Message[] | undefined {\n        return messages.getArray(Message, 'items');\n    }\n}\n```\n\nTo get a single embedded object, you can use `get()` method.\n\n```ts\n@Injectable({ providedIn: 'root' })\nexport class MessageService {\n    // ...\n\n    one(messages: Collection\u003cMessage\u003e): Message | undefined {\n        return messages.get(Message, 'selected');\n    }\n}\n```\n\nNote, `Collection` is a subclass of `Resource`, so both methods can be\nused just fine. Also, it is worth mentioning, that using `get()` on an\nembedded array will return the first element, and using `getArray()` on\na single embedded resource will return an array containing the resource.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgranito-source%2Fngx-hal-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgranito-source%2Fngx-hal-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgranito-source%2Fngx-hal-client/lists"}