{"id":15130277,"url":"https://github.com/tgorka/amplify-datastore-rxjs","last_synced_at":"2026-02-14T14:32:03.648Z","repository":{"id":57177465,"uuid":"415076517","full_name":"tgorka/amplify-datastore-rxjs","owner":"tgorka","description":"RxJs Subjects to work with AWS Amplify and Amplify Datastore.","archived":false,"fork":false,"pushed_at":"2022-02-07T03:02:09.000Z","size":494,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-20T01:58:19.423Z","etag":null,"topics":["amplify","amplifydatastore","angular","aws","awsamplify","data","datastore","fetch","graphql","graphql-client","ionic","rxjs","scroll","typescript"],"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/tgorka.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":null,"support":null}},"created_at":"2021-10-08T17:40:02.000Z","updated_at":"2021-10-30T21:13:16.000Z","dependencies_parsed_at":"2022-09-14T02:00:57.871Z","dependency_job_id":null,"html_url":"https://github.com/tgorka/amplify-datastore-rxjs","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/tgorka/amplify-datastore-rxjs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgorka%2Famplify-datastore-rxjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgorka%2Famplify-datastore-rxjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgorka%2Famplify-datastore-rxjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgorka%2Famplify-datastore-rxjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tgorka","download_url":"https://codeload.github.com/tgorka/amplify-datastore-rxjs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tgorka%2Famplify-datastore-rxjs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29447227,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T14:10:32.461Z","status":"ssl_error","status_checked_at":"2026-02-14T14:09:49.945Z","response_time":53,"last_error":"SSL_read: 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":["amplify","amplifydatastore","angular","aws","awsamplify","data","datastore","fetch","graphql","graphql-client","ionic","rxjs","scroll","typescript"],"created_at":"2024-09-26T02:43:38.003Z","updated_at":"2026-02-14T14:32:03.634Z","avatar_url":"https://github.com/tgorka.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# amplify-datastore-rxjs\n\n[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) \n[![PUBLISHING](https://github.com/tgorka/amplify-datastore-rxjs/actions/workflows/main.yml/badge.svg)](https://github.com/tgorka/amplify-datastore-rxjs/actions/workflows/main.yml) \n[![NPM version](https://img.shields.io/npm/v/amplify-datastore-rxjs.svg?style=flat)](https://npmjs.org/package/amplify-datastore-rxjs)\n\nRxJs Subjects to work with AWS Amplify and Amplify Datastore.\n\n## Table of Contents\n\n* [Description](#description)\n* [Requirements](#requirements)\n* [Installation](#installation)\n* [Usage](#usage)\n* [Examples](#examples)\n* [Author](#author)\n* [License](#license)\n\n## Description\n\n- RxJS Suject helps with loading paginating data \n- Subscribing to data change and re-fetching subsciption event\n- AWS Amplify Datastore adapter for fetching and subscription\n- SDK custom adapter for subscription and re-retching (wotks with amplify generated graphql API)\n- Contains helper to work with the list with infinitive scroll using Ionic \n- Contains helper with trackBy using `id` and `_version` property for angular lists\n- Sujects are having trigger functions that can be used for logging or customizable the process.\n\n## Requirements\n\nIn order to use amplify datastore:\n- Amplify datastore Model configured on server site\n- AWS/Amplify Configuration already setup in the frontend project:\n``` typescript\nAmplify.configure(CONFIG);\n```\n\n## Installation\n\n```\nnpm install amplify-datastore-rxjs\n```\n\n## Usage\n\nLibrary is simplified fetching and subscription enclosing it in the RxJS BehaviorSubject. \n- There are 2 classes for the BehaviorSubject:\n  - ```DataGetSuject``` for the single data record fetched by id\n  - ```DataQuerySuject``` for the list of the data records fetched by the criteria using batches\n- There is default object (```null``` or ```[]```) serve before the first time data is fetched from the source.\n- After calling ```init``` the data is fetched and subscription to the data changes is setup.\n- After subscription notify for the changes the data is re-fetch from the source and the RxJS pipe is pushed with the next data.\n- Adapter in the constructor for customizing data source get/query/subscribe/parse operation.\n- There are adapters and classes with encapsulating this adapters for **Amplify datasource** and **Custom SDK** source (that could be used for custom graphQL, REST, RPC, SOAP, SDK/lib ...).\n  - ```DataStoreGetApiAdapter```, ```DataStoreQueryApiAdapter``` adapters for **Amplify datasource**.\n  - ```DataStoreGetSuject```, ```DataStoreQuerySuject``` BehaviorSujects for **Amplify datasource** (with wrapped adapters).\n  - ```SdkGetApiAdapter```, ```SdkQueryApiAdapter``` adapters for ***Custom SDK***.\n  - ```SdkGetSuject```, ```SdkQuerySuject``` BehaviorSujects for **Custom SDK** (with wrapped adapters).\n  - ```GetApiAdapter```, ```QueryApiAdapter``` interfaces for the adapters.\n  - ```AbstractGetApiAdapter```, ```AbstractQueryApiAdapter``` partly pre-implemented classes for building custom adapters.\n- There are helpfull methods in ```DataQuerySuject```.\n  - ```scrollNextEvent``` can be used for ifinitive scroll (with **ion-scroll** from **Ionic**) of by throwing an event for fetching next batch of the data.\n  - ```trackBy``` can be used to speedup display large set of data records in **Angular** using conbination of ```id``` and ```_version``` (if exists) data properties.\n- There are trigger functions that can be setup to infiltrate data flow (for logs, data manipulation in the process, event interceptions, ...).\n  - ```postDataUpdateTrigger```, ```postDataBatchTrigger``` infiltrating raw data in the process fetched from the source. Can be used for logs or modification of the data from the source that is too specific for having/modifing custom adapter for the data souce.\n  - ```postFetchTrigger``` trigger after new data has been set into the RxJS pipe. Can be used for custom view refresh triggered if not detected autmaticly.\n  - ```dataChangedTrigger``` only on ```DataQuerySuject``` triger after subscription to the data has been triggered but more than first batch of data has been feteched already. The data is **NOT** automaticly refreshed. It can be used to showing to the used that viewing data set has been mutated and can be refreshed manually (using ```init``` method). If only one batch of data has been fetched the data is automaticly refreshed and ```postFetchTrigger``` can be used for infitrating instead.\n\n## Examples \n\n### Requirements\n\nAmplify datastore Test model taken as an example for datastore and api angular service generated from the model used for Custom SDK usage.\nThe Test ```schema.graphql``` for the amplify example:\n\n```graphql\ntype Test @model {\n  id: ID!\n  text: String\n}\n\ntype Subscription {\n  onMutateTest(id: ID): Test @aws_subscribe(mutations: [\"createTest\", \"updateTest\", \"deleteTest\"])\n}\n```\n\nThe subscription part is used **only** for ```SdkGetSuject``` and ```SdkQuerySuject```.\n\n### Amplify Datastore\n\n#### DataStoreGetSuject\n\n##### declaration\n\n``` typescript\nimport { Test } from 'src/models'; // Model generated from **amplify codegen**\nimport { DataGetSuject, DataStoreGetSuject } from 'amplify-datastore-rxjs';\n\n// declare suject with model\n// amplify datastore model is used to determinate how to fetch/subscribe data from the source\nconst test$: DataGetSuject\u003cTest\u003e = new DataStoreGetSuject(Test); \n// if the id of the data is known already the initialization can be done by: \n// new DataStoreGetSuject(Test, 'TEST_RECORD_ID')\n// if the value before fetching (or befor initialization) should be different than null it can be placed as third parameter in the constructor:\n// new DataStoreGetSuject(Test, 'TEST_RECORD_ID', initialTestObj) or new DataStoreGetSuject(Test, null, initialTestObj)\n``` \n\n##### initialion\n\nCan be placed when id of the data record is known or has been changed. Even if id is set this method needs to be called for start initial fetch.\n\n``` typescript\nawait test$.init('TEST_RECORD_ID');\n``` \n\n##### subscription (independend from initialization)\n\n``` typescript\n// subscribe to see the changes (can be called before init - initial value is null)\nconst subscription = test$.subscribe(async test: Test =\u003e {\n  // logic when initial/new version of the Test occured\n  if (!!test) { // in case subscribe was before init\n    console.info(`got test with id:${test?.id} version:${test?._version}`);\n  }\n});\n``` \n##### unsubscription (onDestroy)\n\n``` typescript\n// when changes intercepting is not needed anymore\nsubscription?.unsubscribe();\n```\n\n##### Full Angular test.component.ts example\n\ntaken id from query parameters\n\n``` typescript\nimport { ChangeDetectorRef, ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { DataStoreGetSuject, DataGetSuject } from 'amplify-datastore-rxjs';\nimport { Observable } from 'rxjs';\nimport { Test } from 'src/models';\n\n@Component({\n  selector: 'test',\n  templateUrl: './test.component.html',\n  styleUrls: ['./test.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TestComponent implements OnInit, OnDestroy {\n  \n  testID$: Observable\u003cstring\u003e = this.route.queryParamMap.pipe(map(paramMap =\u003e paramMap.get('test')));\n  testIDSubscription: any = null;\n  test$: DataGetSuject\u003cTest\u003e = new DataStoreGetSuject(Test);\n\n  constructor(\n    private route: ActivatedRoute,\n    private ref: ChangeDetectorRef,\n  ) {\n    this.test$.setPostFetchTrigger(() =\u003e {\n      this.ref.detectChanges();\n      console.info(`loaded test`);\n    });\n    this.test$.setPostDataUpdateTrigger(async (test: Test): Promise\u003cTest\u003e =\u003e {\n        console.info(`fetched test with id:${test.id}|_version:${test._version}`);\n        return test;\n    });\n  }\n\n  async ngOnInit() {\n    this.testIDSubscription?.unsubscribe();\n    this.testIDSubscription = this.testID$.subscribe(async testID =\u003e {\n      if (!!testID) {\n        await this.test$.init(testID);\n      } else {\n        console.warn(`there is no test in the parameters: ${testID}`);\n      }\n    });\n  }\n\n  ngOnDestroy(): void {\n    this.testIDSubscription?.unsubscribe();\n  }\n}\n```\n\n##### Full Angular (Ionic) test.component.html example\n\nangular async pipe manage unsubscription with unsubscription from amplify datastore\n\n``` html\n\u003cion-header\u003e\n  \u003cion-toolbar color=\"primary\"\u003e\n    \u003cion-title\u003e{{ 'Test' }}\u003c/ion-title\u003e\n  \u003c/ion-toolbar\u003e\n\u003c/ion-header\u003e\n\n\u003cion-content class=\"animated fadeIn\"\u003e\n  \u003cion-card class=\"ion-no-margin\" *ngIf=\"test$ | async as test\"\u003e\n    \u003cion-card-content\u003e\u003cion-text color=\"primary\"\u003e{{ test.text }}\u003c/ion-text color=\"primary\"\u003e\u003c/ion-card-content\u003e\n  \u003c/ion-card\u003e\n\u003c/ion-content\u003e\n```\n\n#### DataStoreQuerySuject\n\n##### declaration\n\n``` typescript\nimport { Test } from 'src/models'; // Model generated from **amplify codegen**\nimport { DataQuerySuject, DataStoreQuerySuject } from 'amplify-datastore-rxjs';\n\n// declare suject with model\n// amplify datastore model is used to determinate how to fetch/subscribe data from the source\nconst tests$: DataQuerySuject\u003cTest\u003e = new DataStoreQuerySuject(Test); \n// if the critria of the data filtering is known already the initialization can be done by: \n// new DataStoreGetSuject(Test, testCriteria)\n// if the value before fetching (or befor initialization) should be different than [] it can be placed as third parameter in the constructor:\n// new DataStoreGetSuject(Test, testCriteria, initialTestList) or new DataStoreGetSuject(Test, null, initialTestList)\n``` \n\n##### initialion\n\nCan be placed when criteria of the data record is known or has been changed. Even if criteria is set this method needs to be called for start initial fetch.\nIf criteria is not set ALL is the default strategy.\n\n``` typescript\nawait tests$.init();\n``` \n\n##### subscription (independend from initialization)\n\n``` typescript\n// subscribe to see the changes (can be called before init - initial value is null)\nconst subscription = tests$.subscribe(async tests: Test[] =\u003e {\n  // logic when initial/new version/new batch of the Test occured\n  if (!!tests) { // in case subscribe was before init\n    console.info(`got new ${tests.length} tests in the batch.`);\n  }\n});\n``` \n##### unsubscription (onDestroy)\n\n``` typescript\n// when changes intercepting is not needed anymore\nsubscription?.unsubscribe();\n```\n\n##### Full Angular tests.component.ts example\n\ntaken id from query parameters\n\n``` typescript\nimport { ChangeDetectorRef, ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { DataStoreQuerySuject, DataQuerySuject } from 'amplify-datastore-rxjs';\nimport { Test } from 'src/models';\n\n@Component({\n  selector: 'tests',\n  templateUrl: './tests.component.html',\n  styleUrls: ['./tests.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TestsComponent implements OnInit {\n  \n  tests$: DataQuerySuject\u003cTest\u003e = new DataStoreQuerySuject(Test);\n\n  constructor(\n    private ref: ChangeDetectorRef,\n  ) {\n    this.tests$.setPostFetchTrigger(() =\u003e {\n      this.ref.detectChanges();\n      console.info(`loaded batch of tests`);\n    });\n    this.tests$.setDataChangedTrigger(async () =\u003e {\n        console.info(`data changed in tests =\u003e re-init`);\n        await this.tests$.init(); // reset all sets of data and load the first batch\n    });\n    this.tests$.setPostDataBatchTrigger(async (tests: Test[]): Promise\u003cTest[]\u003e =\u003e {\n        console.info(`got new ${tests.length} tests in the batch.`);\n        return tests;\n    });\n  }\n\n  async ngOnInit() {\n    await this.tests$.init();\n  }\n}\n```\n\n##### Full Angular (Ionic) tests.component.html example\n\nangular async pipe manage unsubscription with unsubscription from amplify datastore.\ntrackBy method is used for the display helper.\nscrollNextEvent method is used for the infinitive scoll.\n\n``` html\n\u003cion-header\u003e\n  \u003cion-toolbar color=\"primary\"\u003e\n    \u003cion-title\u003e{{ 'Tests' }}\u003c/ion-title\u003e\n  \u003c/ion-toolbar\u003e\n\u003c/ion-header\u003e\n\n\u003cion-content class=\"animated fadeIn\"\u003e\n  \u003cion-list class=\"ion-no-padding\" *ngIf=\"tests$ | async as tests\" \u003e\n    \u003cion-item tappable *ngFor=\"let test of tests; trackBy: tests$.trackBy\"\u003e\n      \u003cion-label\u003e\u003cion-text color=\"primary\"\u003e{{ test.text }}\u003c/ion-text\u003e\u003c/ion-label\u003e\n    \u003c/ion-item\u003e\n  \u003c/ion-list\u003e\n  \n  \u003cion-infinite-scroll threshold=\"100px\" position=\"bottom\" (ionInfinite)=\"tests$.scrollNextEvent($event)\"\u003e\n    \u003cion-infinite-scroll-content\n        loadingSpinner=\"bubbles\"\n        loadingText=\"Loading more data...\"\u003e\n    \u003c/ion-infinite-scroll-content\u003e\n  \u003c/ion-infinite-scroll\u003e\n  \n\u003c/ion-content\u003e\n```\n\n### Custom SDK\n\nThe only differece is in the declaration part where setting methods for a custom adapter for the data source, so the example will show with context of full angular file.\nThe example is using API service that was generated by **Amplify codegen** that is wrapper for custom graphQL calls with types declaration.\n\n#### SdkGetSuject\n\n##### Full Angular test.component.ts example\n\n``` typescript\nimport { ChangeDetectorRef, ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { SdkGetSuject, DataGetSuject } from 'amplify-datastore-rxjs';\nimport { Observable } from 'rxjs';\nimport { APIService, GetTestQuery } from 'src/app/API.service';\n\n@Component({\n  selector: 'test',\n  templateUrl: './test.component.html',\n  styleUrls: ['./test.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TestComponent implements OnInit, OnDestroy {\n  \n  testID$: Observable\u003cstring\u003e = this.route.queryParamMap.pipe(map(paramMap =\u003e paramMap.get('test')));\n  testIDSubscription: any = null;\n  test$: DataGetSuject\u003cGetTestQuery\u003e = new SdkGetSuject(\n    this.api.GetTest.bind(this.api),\n    this.api.OnMutateTestListener.bind(this.api),\n  );\n  /*\n   * If data type fetched from the server \n   * (ex. GetTestQueryServer - GetTestQuery would be iside item) \n   * would be different from the one used in the app (GetTestQuery):\n   *\n   * test$: DataGetSuject\u003cGetTestQuery, GetTestQueryServer\u003e = new SdkGetSuject(\n   *   this.api.GetTest.bind(this.api),\n   *   this.api.OnMutateTestListener.bind(this.api),\n   *   data =\u003e data.item,\n   * );\n   *\n   */\n\n  constructor(\n    private route: ActivatedRoute,\n    private ref: ChangeDetectorRef,\n    private api: APIService,\n  ) {\n    this.test$.setPostFetchTrigger(() =\u003e {\n      this.ref.detectChanges();\n      console.info(`loaded test`);\n    });\n    this.test$.setPostDataUpdateTrigger(async (test: Test): Promise\u003cTest\u003e =\u003e {\n        console.info(`fetched test with id:${test.id}|_version:${test._version}`);\n        return test;\n    });\n  }\n\n  async ngOnInit() {\n    this.testIDSubscription?.unsubscribe();\n    this.testIDSubscription = this.testID$.subscribe(async testID =\u003e {\n      if (!!testID) {\n        await this.test$.init(testID);\n      } else {\n        console.warn(`there is no test in the parameters: ${testID}`);\n      }\n    });\n  }\n\n  ngOnDestroy(): void {\n    this.testIDSubscription?.unsubscribe();\n  }\n}\n```\n\n#### SdkQuerySuject\n\n##### Full Angular tests.component.ts example\n\n``` typescript\nimport { ChangeDetectorRef, ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { SdkQuerySuject, DataQuerySuject } from 'amplify-datastore-rxjs';\nimport { APIService, GetTestQuery, ListTestsQuery, ModelTestFilterInput } from 'src/app/API.service';\n\n@Component({\n  selector: 'tests',\n  templateUrl: './tests.component.html',\n  styleUrls: ['./tests.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class TestsComponent implements OnInit {\n  \n  tests$: DataQuerySuject\u003cGetTestQuery, ModelTestFilterInput, ListTestsQuery\u003e = new SdkQuerySuject(\n    (criteria, options) =\u003e this.api.ListTests(criteria, options?.limit, options?.nextToken),\n    this.api.OnMutateTestListener.bind(this.api),\n    data =\u003e data?.items,\n    data =\u003e data?.nextToken, // if this is not set or null, the pagination is taken by pages/limit\n    //data =\u003e data?.currentToken, \u003c- use this if currentToken is given by the API\n  );\n\n  constructor(\n    private ref: ChangeDetectorRef,\n    private api: APIService,\n  ) {\n    this.tests$.setPostFetchTrigger(() =\u003e {\n      this.ref.detectChanges();\n      console.info(`loaded batch of tests`);\n    });\n    this.tests$.setDataChangedTrigger(async () =\u003e {\n        console.info(`data changed in tests =\u003e re-init`);\n        await this.tests$.init(); // reset all sets of data and load the first batch\n    });\n    this.tests$.setPostDataBatchTrigger(async (tests: Test[]): Promise\u003cTest[]\u003e =\u003e {\n        console.info(`got new ${tests.length} tests in the batch.`);\n        return tests;\n    });\n  }\n\n  async ngOnInit() {\n    await this.tests$.init();\n  }\n}\n``` \n\n### DataStore object\n\nIn order not to install ```@aws-amplify/datastore``` in your project again there is a ```DataStore``` re-exported for the custom usage:\n\n``` typescript\nimport { DataStore, Predicates } from 'amplify-datastore-rxjs';\n``` \n\n## Author\nTomasz Górka \u003chttp://tomasz.gorka.org.pl\u003e\n\n## License\n\u0026copy; 2021 Tomasz Górka\n\nMIT licensed.\n\nHave fun with using `amplify-datastore-rxjs` ;).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftgorka%2Famplify-datastore-rxjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftgorka%2Famplify-datastore-rxjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftgorka%2Famplify-datastore-rxjs/lists"}