{"id":13727304,"url":"https://github.com/morrys/relay-angular","last_synced_at":"2025-04-19T23:53:19.409Z","repository":{"id":39139640,"uuid":"255415558","full_name":"morrys/relay-angular","owner":"morrys","description":"Relay \u0026 Relay Offline for Angular","archived":false,"fork":false,"pushed_at":"2023-03-04T12:03:44.000Z","size":7340,"stargazers_count":27,"open_issues_count":50,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-19T23:53:14.481Z","etag":null,"topics":["angular","graphql","ngx","offline","offline-first","persistence","relay","storage","wora"],"latest_commit_sha":null,"homepage":"https://morrys.github.io/relay-angular/docs/relay-angular","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/morrys.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":["morrys"],"custom":"https://www.paypal.me/m0rrys"}},"created_at":"2020-04-13T18:53:50.000Z","updated_at":"2023-02-12T17:05:11.000Z","dependencies_parsed_at":"2024-01-07T21:16:43.413Z","dependency_job_id":null,"html_url":"https://github.com/morrys/relay-angular","commit_stats":{"total_commits":117,"total_committers":2,"mean_commits":58.5,"dds":0.4786324786324786,"last_synced_commit":"01488d48e2b41f2beb6c051b47e3b11810922c91"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morrys%2Frelay-angular","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morrys%2Frelay-angular/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morrys%2Frelay-angular/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morrys%2Frelay-angular/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/morrys","download_url":"https://codeload.github.com/morrys/relay-angular/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249830852,"owners_count":21331357,"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":["angular","graphql","ngx","offline","offline-first","persistence","relay","storage","wora"],"created_at":"2024-08-03T01:03:48.918Z","updated_at":"2025-04-19T23:53:19.392Z","avatar_url":"https://github.com/morrys.png","language":"TypeScript","readme":"# [@relay-angular](https://github.com/morrys/relay-angular)\n\nRelay for Angular \n\n## Contributing\n\n* **Give a star** to the repository and **share it**, you will **help** the **project** and the **people** who will find it useful\n\n* **Create issues**, your **questions** are a **valuable help**\n\n* **PRs are welcome**, but it is always **better to open the issue first** so as to **help** me and other people **evaluating it**\n\n* **Please sponsor me**\n\n## Installation\n\nInstall relay-angular using yarn or npm:\n\n```\nyarn add relay-angular relay-runtime\n```\n\nInstall relay-angular-plugin \u0026 relay-compiler using yarn or npm:\n\n```\nyarn add relay-angular-plugin ngx-build-plus relay-compiler relay-config\n```\n\n## Configuration\n\n### relay\n\nconfiguration file:\n\n```js\n// relay.config.js\nmodule.exports = {\n    // ...\n    // Configuration options accepted by the `relay-compiler` command-line tool,  `babel-plugin-relay` and `relay-angular-plugin`.\n    src: './src',\n    schema: '../server/data/schema.graphql',\n    language: 'typescript',\n    artifactDirectory: './src/__generated__/relay/',\n};\n```\n\n### ngx-build-plus\n\n[see ngx-build-plus getting started](https://github.com/manfredsteyer/ngx-build-plus#getting-started)\n\n* angular.json\n\nChange the builder to serve and build\n\n```\n\"build\": {\n  \"builder\": \"ngx-build-plus:browser\",\n  ...\n}\n\"serve\": {\n  \"builder\": \"ngx-build-plus:dev-server\",\n   ...\n}\n```\n\n### package.json\n\n```\n\"scripts\": {\n    ...\n    \"build\": \"ng build --plugin relay-angular-plugin\",\n    \"start\": \"ng serve --plugin relay-angular-plugin\",\n    \"compile\": \"relay-compiler\"\n    ...\n}    \n```\n## RelayProvider\n\n```ts\nimport { RelayProvider } from 'relay-angular';\nimport { Environment, Network, RecordSource, Store } from 'relay-runtime';\n\nasync function fetchQuery(operation, variables, cacheConfig, uploadables) {\n    const response = await fetch('http://localhost:3000/graphql', {\n        method: 'POST',\n        headers: {\n            'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n            query: operation.text,\n            variables,\n        }),\n    });\n\n    return response.json();\n}\n\nconst modernEnvironment: Environment = new Environment({\n    network: Network.create(fetchQuery),\n    store: new Store(new RecordSource()),\n});\n\n@NgModule({\n  declarations: [\n    ...\n  ],\n  imports: [\n    ...\n  ],\n  providers: [[RelayProvider(modernEnvironment)]],\n  bootstrap: [AppComponent]\n})\nexport class AppModule {\n}\n```\n\n## EnvironmentContext\n\nuse this for change environment\n\n```ts\nimport { Component } from '@angular/core';\nimport { EnvironmentContext } from 'relay-angular';\nimport EnvironmentError from '../relay/errorRelay';\nimport EnvironmentRight from '../relay/relay';\n\n@Component({\n    selector: 'app-root',\n    templateUrl: './app.component.html',\n    styleUrls: ['./app.component.css'],\n})\nexport class AppComponent \n    constructor(private environmentContext: EnvironmentContext) {}\n\n    handle click button\n    handleRightEnv() {\n        this.environmentContext.next(EnvironmentRight);\n    }\n\n    handle click button\n    handleWrongEnv() {\n        this.environmentContext.next(EnvironmentError);\n    }\n}\n```\n\n## @Query\n\n```ts\nimport { Component, Input } from '@angular/core';\nimport { graphql } from 'relay-runtime';\nimport { Query } from 'relay-angular';\nimport { todoQueryQuery } from '../../__generated__/relay/todoQueryQuery.graphql';\n\nexport const QueryApp = graphql`\n    query todoQueryQuery($userId: String) {\n        user(id: $userId) {\n            id\n            ...todoApp_user\n        }\n    }\n`;\n\n@Component({\n    selector: 'todo-query',\n    templateUrl: './todo-query.component.html',\n    styleUrls: ['./todo-query.component.css'],\n})\nexport class TodoQueryComponent {\n    @Input()\n    userId;\n\n    @Query\u003ctodoQueryQuery\u003e(function() {\n        return {\n            query: QueryApp,\n            variables: { userId: this.userId },\n        };\n    })\n    result;\n}\n```\n```html\n \u003ctodo-app *ngIf=\"result \u0026\u0026 result.props \u0026\u0026 result.props.user; else loading\"\n  [fragmentRef]=\"result.props.user\"\u003e\n  {{result}}\n \u003c/todo-app\u003e\n \u003cng-template #loading\u003e\n    \u003cdiv *ngIf=\"!result.error; else error\"\u003e\n      Loading...\u003c/div\u003e\n \u003c/ng-template\u003e\n \u003cng-template #error\u003e\n    \u003cdiv\u003e\n      Error {{ result.error }}\n    \u003c/div\u003e\n \u003c/ng-template\u003e\n```\n\n## @Fragment\n\n```ts\nimport { Component, Input } from '@angular/core';\nimport { Fragment } from 'relay-angular';\nimport { graphql } from 'relay-runtime';\nimport { todoListItem_todo$key, todoListItem_todo$data } from '../../__generated__/relay/todoListItem_todo.graphql';\n\nconst fragmentNode = graphql`\n    fragment todoListItem_todo on Todo {\n        complete\n        id\n        text\n    }\n`;\n\n@Component({\n    selector: 'app-todo-list-item',\n    templateUrl: './todo-list-item.component.html',\n    styleUrls: ['./todo-list-item.component.css'],\n})\nexport class TodoListItemComponent {\n    @Input()\n    fragmentRef: todoListItem_todo$key;\n\n    @Fragment\u003ctodoListItem_todo$key\u003e(function() {\n        return {\n            fragmentNode,\n            fragmentRef: this.fragmentRef,\n        };\n    })\n    todo: todoListItem_todo$data;\n}\n```\n\n## @Refetch\n\n```ts\nimport { Component, Input } from '@angular/core';\nimport { Refetch, RefetchDecorator } from 'relay-angular';\nimport { graphql } from 'relay-runtime';\nimport { todoListFooter_user$data } from '../../__generated__/relay/todoListFooter_user.graphql';\nimport { QueryApp } from '../todo-query/todo-query.component';\n\nconst fragmentNode = graphql`\n    fragment todoListFooter_user on User {\n        id\n        userId\n        completedCount\n        todos(\n            first: 2147483647 # max GraphQLInt\n        ) @connection(key: \"TodoList_todos\") {\n            edges {\n                node {\n                    id\n                    complete\n                }\n            }\n        }\n        totalCount\n    }\n`;\n\n@Component({\n    selector: 'app-todo-list-footer',\n    templateUrl: './todo-list-footer.component.html',\n    styleUrls: ['./todo-list-footer.component.css'],\n})\nexport class TodoListFooterComponent {\n    @Input()\n    fragmentRef: any;\n\n    @Refetch((_this) =\u003e ({\n        fragmentNode,\n        fragmentRef: _this.fragmentRef,\n    }))\n    data: RefetchDecorator\u003ctodoListFooter_user$data\u003e;\n\n    // handle button click\n    handleRefresh() {\n        const { refetch, userId } = this.data;\n        refetch(QueryApp, {\n            userId,\n        });\n    }\n}\n```\n\n```html\n\u003cfooter class=\"footer\"\u003e\n  \u003cspan class=\"todo-count\"\u003e\u003cstrong\u003e{{data.totalCount - data.completedCount}}\u003c/strong\u003e {{data.totalCount - data.completedCount == 1 ? 'item' : 'items'}} left\u003c/span\u003e\n  \u003cbutton \n          class=\"clear-completed\"\n          (click)=\"handleRefresh($event)\"\u003e\n          Refresh\n  \u003c/button\u003e\n\u003c/footer\u003e\n```\n\n## @Pagination\n\n```ts\nimport { Component, Input } from '@angular/core';\nimport { Pagination, PaginationDecorator } from 'relay-angular';\nimport { graphql } from 'relay-runtime';\nimport { todoListFooter_user$data } from '../../__generated__/relay/todoListFooter_user.graphql';\nimport { QueryApp } from '../todo-query/todo-query.component';\n\nconst fragmentSpec = graphql`\n  fragment todoListFooter_user on User {\n    id\n    idUser\n    todos(idUser: $idUser, first: $first, after: $after)\n      @connection(key: \"TodoList_todos\", filters: [\"idUser\"]) {\n      nextToken\n      edges {\n         node {\n             id\n             complete\n         }\n      }\n      pageInfo {\n        hasNextPage\n        endCursor\n      }\n    }\n  }\n`;\n\nconst connectionConfig = {\n  query: QueryApp,\n  getVariables: (props, paginationInfo) =\u003e ({\n    first: 2,\n    after: paginationInfo.cursor,\n    idUser: props.idUser\n  })\n};\n\n@Component({\n    selector: 'app-todo-list-footer',\n    templateUrl: './todo-list-footer.component.html',\n    styleUrls: ['./todo-list-footer.component.css'],\n})\nexport class TodoListFooterComponent {\n    @Input()\n    fragmentRef: any;\n\n    @Pagination((_this) =\u003e ({\n        fragmentNode,\n        fragmentRef: _this.fragmentRef,\n    }))\n    data: PaginationDecorator\u003ctodoListFooter_user$data\u003e;\n\n    // handle button click\n    handleLoadMore() {\n        const { hasMore, isLoading, loadMore } = this.data;\n        hasMore() \u0026\u0026 !isLoading() \u0026\u0026 loadMore(connectionConfig, 2, () =\u003e null, undefined)\n    }\n}\n```\n\n## @RelayEnvironment\n\n```ts\nimport { Component } from '@angular/core';\nimport { RelayEnvironment } from 'relay-angular';\nimport { graphql, Environment } from 'relay-runtime';\n@Component({\n    selector: 'todo-app',\n    templateUrl: './todo-app.component.html',\n    styleUrls: ['./todo-app.component.css'],\n})\nexport class TodoAppComponent {\n\n    @RelayEnvironment()\n    environment: Environment;\n}\n```\n\n## mutate\n\n\ncreate mutation\n\n```ts changeTodoStatusMutation.ts\nimport { mutate } from 'relay-angular';\nimport { graphql } from 'relay-runtime';\n\nexport const mutation = graphql`\n    mutation changeTodoStatusMutation($input: ChangeTodoStatusInput!) {\n        changeTodoStatus(input: $input) {\n            todo {\n                id\n                complete\n            }\n            user {\n                id\n                completedCount\n            }\n        }\n    }\n`;\n\nfunction commit(complete: boolean, todo: any, user: any): any {\n    const input: any = {\n        complete,\n        userId: user.userId,\n        id: todo.id,\n    };\n\n    return mutate({\n        mutation,\n        variables: {\n            input,\n        },\n    });\n}\n\nexport default { commit };\n```\n\nuse it\n\n```\nimport { Component } from '@angular/core';\nimport changeTodoStatus from '../mutations/changeTodoStatus';\n\n@Component({\n    selector: 'app-todo-list-item',\n    templateUrl: './todo-list-item.component.html',\n    styleUrls: ['./todo-list-item.component.css'],\n})\nexport class TodoListItemComponent {\n\n    // handle button\n    toggleTodoComplete(todo, user) {\n        changeTodoStatus.commit(!todo.complete, todo, user);\n    }\n}\n```\n\n","funding_links":["https://github.com/sponsors/morrys","https://www.paypal.me/m0rrys"],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorrys%2Frelay-angular","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorrys%2Frelay-angular","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorrys%2Frelay-angular/lists"}