{"id":13481377,"url":"https://github.com/ghidoz/angular2-jsonapi","last_synced_at":"2025-04-04T09:06:38.349Z","repository":{"id":10182140,"uuid":"64756996","full_name":"ghidoz/angular2-jsonapi","owner":"ghidoz","description":"A lightweight Angular 2 adapter for JSON API","archived":false,"fork":false,"pushed_at":"2023-03-02T12:14:30.000Z","size":1860,"stargazers_count":198,"open_issues_count":87,"forks_count":122,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-03-27T02:07:53.076Z","etag":null,"topics":["angular2","json-api"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ghidoz.png","metadata":{"files":{"readme":"README.MD","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-08-02T12:59:01.000Z","updated_at":"2025-01-03T01:28:44.000Z","dependencies_parsed_at":"2024-01-14T09:00:24.244Z","dependency_job_id":null,"html_url":"https://github.com/ghidoz/angular2-jsonapi","commit_stats":{"total_commits":339,"total_committers":37,"mean_commits":9.162162162162161,"dds":0.7404129793510325,"last_synced_commit":"b1d771da6f6fe60ce79c7c00cb0f8446a251c959"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghidoz%2Fangular2-jsonapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghidoz%2Fangular2-jsonapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghidoz%2Fangular2-jsonapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ghidoz%2Fangular2-jsonapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ghidoz","download_url":"https://codeload.github.com/ghidoz/angular2-jsonapi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246927809,"owners_count":20856193,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["angular2","json-api"],"created_at":"2024-07-31T17:00:51.322Z","updated_at":"2025-04-04T09:06:38.318Z","avatar_url":"https://github.com/ghidoz.png","language":"TypeScript","readme":"# Angular 2 JSON API\n\nA lightweight Angular 2 adapter for [JSON API](http://jsonapi.org/)\n\n[![Build Status](https://travis-ci.org/ghidoz/angular2-jsonapi.svg?branch=master)](https://travis-ci.org/ghidoz/angular2-jsonapi)\n[![Coverage Status](https://coveralls.io/repos/github/ghidoz/angular2-jsonapi/badge.svg?branch=master)](https://coveralls.io/github/ghidoz/angular2-jsonapi?branch=master)\n[![Angular 2 Style Guide](https://mgechev.github.io/angular2-style-guide/images/badge.svg)](https://angular.io/styleguide)\n[![Dependency Status](https://david-dm.org/ghidoz/angular2-jsonapi.svg)](https://david-dm.org/ghidoz/angular2-jsonapi)\n[![devDependency Status](https://david-dm.org/ghidoz/angular2-jsonapi/dev-status.svg)](https://david-dm.org/ghidoz/angular2-jsonapi#info=devDependencies)\n[![npm version](https://badge.fury.io/js/angular2-jsonapi.svg)](https://badge.fury.io/js/angular2-jsonapi)\n\n## Table of Contents\n- [Introduction](#Introduction)\n- [Installation](#installation)\n- [Usage](#usage)\n    - [Configuration](#configuration)\n    - [Finding Records](#finding-records)\n        - [Querying for Multiple Records](#querying-for-multiple-records)\n        - [Retrieving a Single Record](#retrieving-a-single-record)\n    - [Creating, Updating and Deleting](#creating-updating-and-deleting)\n        - [Creating Records](#creating-records)\n        - [Updating Records](#updating-records)\n        - [Persisting Records](#persisting-records)\n        - [Deleting Records](#deleting-records)\n    - [Relationships](#relationships)\n    - [Metadata](#metadata)\n    - [Custom Headers](#custom-headers)\n    - [Error handling](#error-handling)\n    - [Dates](#dates)\n- [Development](#development)\n- [Additional tools](#additional-tools)\n- [License](#licence)\n\n## Introduction\nWhy this library? Because [JSON API](http://jsonapi.org/) is an awesome standard, but the responses that you get and the way to interact with endpoints are not really easy and directly consumable from Angular.\n\nMoreover, using Angular2 and Typescript, we like to interact with classes and models, not with bare JSONs. Thanks to this library, you will be able to map all your data into models and relationships like these:\n\n```javascript\n[\n    Post{\n        id: 1,\n        title: 'My post',\n        content: 'My content',\n        comments: [\n            Comment{\n                id: 1,\n                // ...\n            },\n            Comment{\n                id: 2,\n                // ...\n            }\n        ]\n    },\n    // ...\n]\n```\n\n\n## Installation\n\nTo install this library, run:\n```bash\n$ npm install angular2-jsonapi --save\n```\n\nAdd the `JsonApiModule` to your app module imports:\n```typescript\nimport { JsonApiModule } from 'angular2-jsonapi';\n\n@NgModule({\n  imports: [\n    BrowserModule,\n    JsonApiModule\n  ],\n  declarations: [\n    AppComponent\n  ],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n```\n\n### Angular CLI configuration (for CLI 8.1+)\n\nBeginning from Angular CLI 8.1 the `tsconfig.json` does not sets the `emitDecoratorMetadata` option (see https://blog.ninja-squad.com/2019/07/03/angular-cli-8.1/#typescript-configuration-changes). But we need it to read the metadata from the models. So make sure to update your `tsconfig.json`:\n\n```json\n{\n  \"compilerOptions\": {\n    \"emitDecoratorMetadata\": true,\n  }\n}\n```\n\n### Notice for `es2015` compilation\n\nBeginning with Angular 8 the default compile target will be `es2015` (in `tsconfig.json`). \nMake sure to add this line in your `src/polyfills.ts` as we need it to read metadata from the models:\n\n```typescript\nimport 'core-js/proposals/reflect-metadata';\n```\n\n**Warning**: If you have circular dependencies in your model definitions (see https://github.com/ghidoz/angular2-jsonapi/issues/236#issuecomment-519473153 for example), you need to change the compile target to `es5` as this lead to runtime errors `ReferenceError: Cannot access 'x' before initialization` (see https://github.com/angular/angular/issues/30106#issuecomment-497699838).\n\n## Usage\n\n### Configuration\n\nFirstly, create your `Datastore` service:\n- Extend the `JsonApiDatastore` class\n- Decorate it with `@JsonApiDatastoreConfig`, set the `baseUrl` for your APIs and map your models (Optional: you can set `apiVersion`, `baseUrl` will be suffixed with it)\n- Pass the `HttpClient` depencency to the parent constructor.\n\n```typescript\nimport { JsonApiDatastoreConfig, JsonApiDatastore, DatastoreConfig } from 'angular2-jsonapi';\n\nconst config: DatastoreConfig = {\n  baseUrl: 'http://localhost:8000/v1/',\n  models: {\n    posts: Post,\n    comments: Comment,\n    users: User\n  }\n}\n\n@Injectable()\n@JsonApiDatastoreConfig(config)\nexport class Datastore extends JsonApiDatastore {\n\n    constructor(http: HttpClient) {\n        super(http);\n    }\n\n}\n```\n\nThen set up your models:\n- Extend the `JsonApiModel` class\n- Decorate it with `@JsonApiModelConfig`, passing the `type`\n- Decorate the class properties with `@Attribute`\n- Decorate the relationships attributes with `@HasMany` and `@BelongsTo`\n- (optional) Define your [Metadata](#metadata)\n\n```typescript\nimport { JsonApiModelConfig, JsonApiModel, Attribute, HasMany, BelongsTo } from 'angular2-jsonapi';\n\n@JsonApiModelConfig({\n    type: 'posts'\n})\nexport class Post extends JsonApiModel {\n\n    @Attribute()\n    title: string;\n\n    @Attribute()\n    content: string;\n\n    @Attribute({ serializedName: 'created-at' })\n    createdAt: Date;\n\n    @HasMany()\n    comments: Comment[];\n}\n\n@JsonApiModelConfig({\n    type: 'comments'\n})\nexport class Comment extends JsonApiModel {\n\n    @Attribute()\n    title: string;\n\n    @Attribute()\n    created_at: Date;\n\n    @BelongsTo()\n    post: Post;\n\n    @BelongsTo()\n    user: User;\n}\n\n@JsonApiModelConfig({\n    type: 'users'\n})\nexport class User extends JsonApiModel {\n\n    @Attribute()\n    name: string;\n    // ...\n}\n```\n\n### Finding Records\n\n#### Querying for Multiple Records\n\nNow, you can use your `Datastore` in order to query your API with the `findAll()` method:\n- The first argument is the type of object you want to query.\n- The second argument is the list of params: write them in JSON format and they will be serialized.\n- The returned value is a document which gives access to the metdata and the models.\n```typescript\n// ...\nconstructor(private datastore: Datastore) { }\n\ngetPosts(){\n    this.datastore.findAll(Post, {\n        page: { size: 10, number: 1 },\n        filter: {\n          title: 'My Post',\n        },\n    }).subscribe(\n        (posts: JsonApiQueryData\u003cPost\u003e) =\u003e console.log(posts.getModels())\n    );\n}\n```\n\nUse `peekAll()` to retrieve all of the records for a given type that are already loaded into the store, without making a network request:\n\n```typescript\nlet posts = this.datastore.peekAll(Post);\n```\n\n\n#### Retrieving a Single Record\n\nUse `findRecord()` to retrieve a record by its type and ID:\n\n```typescript\nthis.datastore.findRecord(Post, '1').subscribe(\n    (post: Post) =\u003e console.log(post)\n);\n```\n\nUse `peekRecord()` to retrieve a record by its type and ID, without making a network request. This will return the record only if it is already present in the store:\n\n```typescript\nlet post = this.datastore.peekRecord(Post, '1');\n```\n\n### Creating, Updating and Deleting\n\n#### Creating Records\n\nYou can create records by calling the `createRecord()` method on the datastore:\n- The first argument is the type of object you want to create.\n- The second is a JSON with the object attributes.\n\n```typescript\nthis.datastore.createRecord(Post, {\n    title: 'My post',\n    content: 'My content'\n});\n```\n\n#### Updating Records\n\nMaking changes to records is as simple as setting the attribute you want to change:\n\n```typescript\nthis.datastore.findRecord(Post, '1').subscribe(\n    (post: Post) =\u003e {\n        post.title = 'New title';\n    }\n);\n```\n\n#### Persisting Records\n\nRecords are persisted on a per-instance basis. Call `save()` on any instance of `JsonApiModel` and it will make a network request.\n\nThe library takes care of tracking the state of each record for you, so that newly created records are treated differently from existing records when saving.\n\nNewly created records will be `POST`ed:\n\n```typescript\nlet post = this.datastore.createRecord(Post, {\n    title: 'My post',\n    content: 'My content'\n});\n\npost.save().subscribe();  // =\u003e POST to '/posts'\n```\n\nRecords that already exist on the backend are updated using the HTTP `PATCH` verb:\n\n```typescript\nthis.datastore.findRecord(Post, '1').subscribe(\n    (post: Post) =\u003e {\n        post.title = 'New title';\n        post.save().subscribe();  // =\u003e PATCH to '/posts/1'\n    }\n);\n```\n\nThe `save()` method will return an `Observer` that you need to subscribe:\n\n```typescript\npost.save().subscribe(\n    (post: Post) =\u003e console.log(post)\n);\n```\n\n**Note**: always remember to call the `subscribe()` method, even if you are not interested in doing something with the response. Since the `http` method return a [cold Observable](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md#cold-vs-hot-observables), the request won't go out until something subscribes to the observable.\n\nYou can tell if a record has outstanding changes that have not yet been saved by checking its `hasDirtyAttributes` property.\n\nAt this point, you can either persist your changes via `save()` or you can roll back your changes. Calling `rollbackAttributes()` for a saved record reverts all the dirty attributes to their original value.\n\n```typescript\nthis.datastore.findRecord(Post, '1').subscribe(\n    (post: Post) =\u003e {\n        console.log(post.title);                // =\u003e 'Old title'\n        console.log(post.hasDirtyAttributes);   // =\u003e false\n        post.title = 'New title';\n        console.log(post.hasDirtyAttributes);   // =\u003e true\n        post.rollbackAttributes();\n        console.log(post.hasDirtyAttributes);   // =\u003e false\n        console.log(post.title);                // =\u003e 'Old title'\n    }\n);\n```\n\n#### Deleting Records\n\nFor deleting a record, just call the datastore's method `deleteRecord()`, passing the type and the id of the record:\n\n```typescript\nthis.datastore.deleteRecord(Post, '1').subscribe(() =\u003e {\n    // deleted!\n});\n```\n\n### Relationships\n\n#### Querying records\n\nIn order to query an object including its relationships, you can pass in its options the attribute name you want to load with the relationships:\n\n```typescript\nthis.datastore.findAll(Post, {\n    page: { size: 10, number: 1},\n    include: 'comments'\n}).subscribe(\n    (document) =\u003e {\n        console.log(document.getMeta()); // metadata\n        console.log(document.getModels()); // models\n    }\n);\n```\n\nThe same, if you want to include relationships when finding a record:\n\n```typescript\nthis.datastore.findRecord(Post, '1', {\n    include: 'comments,comments.user'\n}).subscribe(\n    (post: Post) =\u003e console.log(post)\n);\n```\n\nThe library will try to resolve relationships on infinite levels connecting nested objects by reference. So that you can have a `Post`, with a list of `Comment`s, that have a `User` that has `Post`s, that have `Comment`s... etc.\n\n**Note**: If you `include` multiple relationships, **do not** use whitespaces in the `include` string (e.g. `comments, comments.user`) as those will be encoded to `%20` and this results in a broken URL.\n\n#### Creating Records\n\nIf the object you want to create has a **one-to-many** relationship, you can do this:\n\n```typescript\nlet post = this.datastore.peekRecord(Post, '1');\nlet comment = this.datastore.createRecord(Comment, {\n    title: 'My comment',\n    post: post\n});\ncomment.save().subscribe();\n```\n\nThe library will do its best to discover which relationships map to one another. In the code above, for example, setting the `comment` relationship with the `post` will update the `post.comments` array, automatically adding the `comment` object!\n\nIf you want to include a relationship when creating a record to have it parsed in the response, you can pass the `params` object to the `save()` method:\n\n```typescript\ncomment.save({\n    include: 'user'\n}).subscribe(\n    (comment: Comment) =\u003e console.log(comment)\n);\n```\n\n#### Updating Records\n\nYou can also update an object that comes from a relationship:\n\n```typescript\nthis.datastore.findRecord(Post, '1', {\n    include: 'comments'\n}).subscribe(\n    (post: Post) =\u003e {\n        let comment: Comment = post.comments[0];\n        comment.title = 'Cool';\n        comment.save().subscribe((comment: Comment) =\u003e {\n            console.log(comment);\n        });\n    }\n);\n```\n\n### Metadata\nMetadata such as links or data for pagination purposes can also be included in the result.\n\nFor each model a specific MetadataModel can be defined. To do this, the class name needs to be added in the ModelConfig.\n\nIf no MetadataModel is explicitly defined, the default one will be used, which contains an array of links and `meta` property.\n```\n@JsonApiModelConfig({\n    type: 'deals',\n    meta: JsonApiMetaModel\n})\nexport class Deal extends JsonApiModel\n```\n\nAn instance of a class provided in `meta` property will get the whole response in a constructor.\n\n### Datastore config\n\nDatastore config can be specified through the `JsonApiDatastoreConfig` decorator and/or by setting a `config` variable of the `Datastore` class. If an option is specified in both objects, a value from `config` variable will be taken into account.\n\n```typescript\n@JsonApiDatastoreConfig(config: DatastoreConfig)\nexport class Datastore extends JsonApiDatastore {\n    private customConfig: DatastoreConfig = {\n        baseUrl: 'http://something.com'\n    }\n\n    constructor() {\n        this.config = this.customConfig;\n    }\n}\n```\n\n`config`:\n\n* `models` - all the models which will be stored in the datastore\n* `baseUrl` - base API URL\n* `apiVersion` - optional, a string which will be appended to the baseUrl\n* `overrides` - used for overriding internal methods to achive custom functionalities\n\n##### Overrides\n\n* `getDirtyAttributes` - determines which model attributes are dirty\n* `toQueryString` - transforms query parameters to a query string\n\n\n### Model config\n\n```typescript\n@JsonApiModelConfig(options: ModelOptions)\nexport class Post extends JsonApiModel { }\n```\n\n`options`:\n\n* `type`\n* `baseUrl` - if not specified, the global `baseUrl` will be used\n* `apiVersion` - if not specified, the global `apiVersion` will be used\n* `modelEndpointUrl` - if not specified, `type` will be used instead\n* `meta` - optional, metadata model\n\n### Decorators\n\n#### Model decorators\n\n* `Attribute(options: AttributeDecoratorOptions)`\n\n    * `AttributeDecoratorOptions`:\n\n        * `converter`, optional, must implement `PropertyConverter` interface\n        * `serializedName`, optional\n\n### Custom Headers\n\nBy default, the library adds these headers, according to the [JSON API MIME Types](http://jsonapi.org/#mime-types):\n\n```\nAccept: application/vnd.api+json\nContent-Type: application/vnd.api+json\n```\n\nYou can also add your custom headers to be appended to each http call:\n\n```typescript\nthis.datastore.headers = new HttpHeaders({'Authorization': 'Bearer ' + accessToken});\n```\n\nOr you can pass the headers as last argument of any datastore call method:\n\n```typescript\nthis.datastore.findAll(Post, {\n    include: 'comments'\n}, new HttpHeaders({'Authorization': 'Bearer ' + accessToken}));\n```\n\nand in the `save()` method:\n\n```typescript\npost.save({}, new HttpHeaders({'Authorization': 'Bearer ' + accessToken})).subscribe();\n```\n\n### Custom request options\n\nYou can add your custom request options to be appended to each http call:\n\n```typescript\nthis.datastore.requestOptions = {\n    withCredentials: false,\n    myOption: 123\n}\n```\n\n### Error handling\n\nError handling is done in the `subscribe` method of the returned Observables.\nIf your server returns valid [JSON API Error Objects](http://jsonapi.org/format/#error-objects) you can access them in your onError method:\n\n```typescript\nimport {ErrorResponse} from \"angular2-jsonapi\";\n\n...\n\nthis.datastore.findAll(Post).subscribe(\n    (posts: Post[]) =\u003e console.log(posts),\n    (errorResponse) =\u003e {\n        if (errorResponse instanceof ErrorResponse) {\n              // do something with errorResponse\n              console.log(errorResponse.errors);\n        }\n    }\n);\n```\n\nIt's also possible to handle errors for all requests by overriding `handleError(error: any): Observable` in the datastore.\n\n### Dates\n\nThe library will automatically transform date values into `Date` objects and it will serialize them when sending to the server. In order to do that, remember to set the type of the corresponding attribute as `Date`:\n\n```typescript\n@JsonApiModelConfig({\n    type: 'posts'\n})\nexport class Post extends JsonApiModel {\n\n    // ...\n\n    @Attribute()\n    created_at: Date;\n\n}\n```\n\nMoreover, it should be noted that the following assumptions have been made:\n- Dates are expected to be received in one of the ISO 8601 formats, as per the [JSON API spec recommendation](http://jsonapi.org/recommendations/#date-and-time-fields);\n- Dates are always sent in full ISO 8601 format, with local timezone and without milliseconds (example: `2001-02-03T14:05:06+07:00`).\n\n\n## Development\n\nTo generate all `*.js`, `*.js.map` and `*.d.ts` files:\n\n```bash\n$ npm run build\n```\n\nTo lint all `*.ts` files:\n\n```bash\n$ npm run lint\n```\n\n## Additional tools\n* Gem for generating the model definitions based on active model serializers: https://github.com/oncore-education/jsonapi_models\n\n## Thanks\n\nThis library is inspired by the draft of [this never implemented library](https://github.com/beauby/angular2-jsonapi).\n\n## License\n\nMIT © [Daniele Ghidoli](http://danieleghidoli.it)\n","funding_links":[],"categories":["Uncategorized"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghidoz%2Fangular2-jsonapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fghidoz%2Fangular2-jsonapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghidoz%2Fangular2-jsonapi/lists"}