├── .nvmrc ├── README.md ├── packages ├── demo │ ├── src │ │ ├── polyfills.ts │ │ ├── main.ts │ │ ├── index.html │ │ ├── app │ │ │ ├── app.component.ts │ │ │ ├── app.routes.ts │ │ │ ├── app.config.ts │ │ │ └── pages │ │ │ │ ├── movies │ │ │ │ └── movies-page.component.ts │ │ │ │ └── movie │ │ │ │ └── movie-page.component.ts │ │ └── styles.scss │ ├── package.json │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── angular.json └── apollo-angular │ ├── schematics │ ├── utils │ │ ├── index.cts │ │ ├── test.cts │ │ └── file.cts │ ├── tsconfig.test.json │ ├── jasmine.json │ ├── install │ │ ├── schema.cts │ │ ├── files │ │ │ └── module │ │ │ │ └── graphql.module.ts │ │ ├── schema.json │ │ └── index.cts │ ├── tsconfig.json │ ├── collection.json │ ├── README.md │ └── tests │ │ ├── utils.spec.cts │ │ └── ng-add.spec.cts │ ├── tsconfig.spec.json │ ├── http │ ├── ng-package.json │ ├── src │ │ ├── index.ts │ │ ├── types.ts │ │ └── http-link.ts │ ├── tsconfig.json │ ├── package.json │ ├── tests │ │ ├── utils.ts │ │ └── ssr.spec.ts │ └── LICENSE │ ├── headers │ ├── ng-package.json │ ├── tsconfig.json │ ├── src │ │ └── index.ts │ ├── package.json │ ├── LICENSE │ └── tests │ │ └── index.spec.ts │ ├── testing │ ├── ng-package.json │ ├── src │ │ ├── index.ts │ │ ├── operation.ts │ │ ├── module.ts │ │ └── controller.ts │ ├── tsconfig.json │ ├── package.json │ └── tests │ │ ├── utils.ts │ │ ├── module.spec.ts │ │ ├── only-complete-data.spec.ts │ │ └── operation.spec.ts │ ├── test-utils │ ├── matchers.ts │ └── matchers │ │ └── toEmitAnything.ts │ ├── persisted-queries │ ├── ng-package.json │ ├── tsconfig.json │ ├── package.json │ ├── CHANGELOG.md │ ├── src │ │ └── index.ts │ ├── LICENSE │ └── tests │ │ └── persisted-queries.spec.ts │ ├── ng-package.json │ ├── src │ ├── gql.ts │ ├── tokens.ts │ ├── index.ts │ ├── types.ts │ ├── apollo-module.ts │ ├── mutation.ts │ ├── subscription.ts │ ├── query.ts │ ├── only-complete-data.ts │ ├── utils.ts │ └── query-ref.ts │ ├── scripts │ └── move-schematics.js │ ├── tests │ ├── vitest.d.ts │ ├── test-setup.ts │ ├── index.spec.ts │ ├── integration.spec.ts │ ├── Mutation.spec.ts │ └── Subscription.spec.ts │ ├── tsconfig.json │ ├── vite.config.ts │ ├── LICENSE │ ├── package.json │ └── README.md ├── website ├── tailwind.config.ts ├── postcss.config.cjs ├── public │ ├── favicon.png │ └── assets │ │ ├── img │ │ ├── cover.png │ │ ├── devtools │ │ │ ├── devtools.png │ │ │ ├── query-init.png │ │ │ ├── mutation-init.png │ │ │ ├── query-result.png │ │ │ ├── mutation-result.png │ │ │ ├── query-init-data.png │ │ │ └── mutation-result-data.png │ │ └── logo │ │ │ ├── icon-apollo-white-200x200.png │ │ │ └── logo-apollo-space.svg │ │ └── docs │ │ ├── apollo-devtools-store.png │ │ ├── apollo-devtools-queries.png │ │ └── apollo-devtools-graphiql.png ├── src │ ├── pages │ │ ├── index.mdx │ │ ├── docs │ │ │ ├── performance │ │ │ │ ├── _meta.ts │ │ │ │ ├── improving-performance.mdx │ │ │ │ └── optimistic-ui.mdx │ │ │ ├── local-state │ │ │ │ ├── _meta.ts │ │ │ │ ├── reactive-variables.mdx │ │ │ │ └── management.mdx │ │ │ ├── development-and-testing │ │ │ │ ├── _meta.ts │ │ │ │ ├── client-schema-mocking.mdx │ │ │ │ ├── using-typescript.mdx │ │ │ │ └── developer-tools.mdx │ │ │ ├── caching │ │ │ │ ├── _meta.ts │ │ │ │ ├── interaction.mdx │ │ │ │ ├── field-behavior.mdx │ │ │ │ └── garbage-collection.mdx │ │ │ ├── recipes │ │ │ │ ├── _meta.ts │ │ │ │ ├── angular-cli.mdx │ │ │ │ ├── nativescript.mdx │ │ │ │ ├── multiple-clients.mdx │ │ │ │ ├── automatic-persisted-queries.mdx │ │ │ │ └── webpack.mdx │ │ │ ├── data │ │ │ │ ├── _meta.ts │ │ │ │ └── error-handling.mdx │ │ │ ├── _meta.ts │ │ │ ├── migration.mdx │ │ │ └── index.mdx │ │ ├── _app.tsx │ │ └── _meta.ts │ └── components │ │ └── index-page.tsx ├── next-env.d.ts ├── next-sitemap.config.js ├── theme.config.tsx ├── tsconfig.json ├── package.json └── next.config.js ├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.md └── workflows │ ├── release.yml │ ├── pr.yml │ ├── website.yml │ └── main.yml ├── CHANGELOG.md ├── renovate.json ├── prettier.config.cjs ├── .prettierignore ├── .vscode └── settings.json ├── .changeset └── config.json ├── .devcontainer ├── library-scripts │ ├── README.md │ └── node-debian.sh ├── Dockerfile ├── devcontainer.json └── base.Dockerfile ├── tsconfig.json ├── scripts ├── run-e2e-locally.sh ├── bump.js └── prepare-e2e.js ├── LICENSE ├── .gitignore └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/apollo-angular/README.md -------------------------------------------------------------------------------- /packages/demo/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'zone.js'; 2 | -------------------------------------------------------------------------------- /website/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | export { default } from '@theguild/tailwind-config'; 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: PowerKiKi 4 | -------------------------------------------------------------------------------- /website/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('@theguild/tailwind-config/postcss.config'); 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## Main 4 | 5 | - [apollo-angular](./packages/apollo-angular/CHANGELOG.md) 6 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/utils/index.cts: -------------------------------------------------------------------------------- 1 | export * from './file.cjs'; 2 | export * from './test.cjs'; 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "enabled": false 4 | } 5 | -------------------------------------------------------------------------------- /website/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/favicon.png -------------------------------------------------------------------------------- /website/src/pages/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | --- 4 | 5 | export { IndexPage as default } from '../components/index-page'; 6 | -------------------------------------------------------------------------------- /website/public/assets/img/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/cover.png -------------------------------------------------------------------------------- /website/public/assets/img/devtools/devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/devtools/devtools.png -------------------------------------------------------------------------------- /website/public/assets/img/devtools/query-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/devtools/query-init.png -------------------------------------------------------------------------------- /website/public/assets/docs/apollo-devtools-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/docs/apollo-devtools-store.png -------------------------------------------------------------------------------- /website/public/assets/img/devtools/mutation-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/devtools/mutation-init.png -------------------------------------------------------------------------------- /website/public/assets/img/devtools/query-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/devtools/query-result.png -------------------------------------------------------------------------------- /packages/apollo-angular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["vitest/globals", "node"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /website/public/assets/docs/apollo-devtools-queries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/docs/apollo-devtools-queries.png -------------------------------------------------------------------------------- /website/public/assets/img/devtools/mutation-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/devtools/mutation-result.png -------------------------------------------------------------------------------- /website/public/assets/img/devtools/query-init-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/devtools/query-init-data.png -------------------------------------------------------------------------------- /website/public/assets/docs/apollo-devtools-graphiql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/docs/apollo-devtools-graphiql.png -------------------------------------------------------------------------------- /website/public/assets/img/devtools/mutation-result-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/devtools/mutation-result-data.png -------------------------------------------------------------------------------- /website/public/assets/img/logo/icon-apollo-white-200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-guild-org/apollo-angular/HEAD/website/public/assets/img/logo/icon-apollo-white-200x200.png -------------------------------------------------------------------------------- /packages/apollo-angular/http/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", 3 | "lib": { 4 | "entryFile": "src/index.ts" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/apollo-angular/headers/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", 3 | "lib": { 4 | "entryFile": "src/index.ts" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", 3 | "lib": { 4 | "entryFile": "src/index.ts" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/apollo-angular/test-utils/matchers.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'vitest'; 2 | import { toEmitAnything } from './matchers/toEmitAnything'; 3 | 4 | expect.extend({ 5 | toEmitAnything, 6 | }); 7 | -------------------------------------------------------------------------------- /packages/apollo-angular/persisted-queries/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", 3 | "lib": { 4 | "entryFile": "src/index.ts" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/apollo-angular/http/src/index.ts: -------------------------------------------------------------------------------- 1 | // http 2 | export { HttpLink, HttpLinkHandler } from './http-link'; 3 | // http-batch 4 | export { HttpBatchLink, HttpBatchLinkHandler } from './http-batch-link'; 5 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "emitDecoratorMetadata": true 5 | }, 6 | "include": ["tests/*.spec.cts"] 7 | } 8 | -------------------------------------------------------------------------------- /website/src/pages/docs/performance/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'improving-performance': 'Improving Performance', 3 | 'optimistic-ui': 'Optimistic UI', 4 | 'server-side-rendering': 'Server Side Rendering', 5 | }; 6 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('@theguild/prettier-config'), 3 | overrides: [], // We don't want to remove semicolon in mdx files, see https://github.com/the-guild-org/shared-config/pull/11 4 | }; 5 | -------------------------------------------------------------------------------- /website/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app'; 2 | import '@theguild/components/style.css'; 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /website/src/pages/docs/local-state/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | management: 'Managing Local State', 3 | 'managing-state-with-field-policies': 'Managing State with Field Policies', 4 | 'reactive-variables': 'Reactive Variables', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_files": ["build/schematics/tests/*spec.cjs"], 3 | "env": { 4 | "stopSpecOnExpectationFailure": false, 5 | "random": false, 6 | "forbidDuplicateNames": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/apollo-angular/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "build", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | }, 7 | "allowedNonPeerDependencies": ["extract-files"] 8 | } 9 | -------------------------------------------------------------------------------- /website/src/pages/docs/development-and-testing/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'using-typescript': 'TypeScript', 3 | testing: 'Testing', 4 | 'client-schema-mocking': 'Mocking New Schema Capabilities', 5 | 'developer-tools': 'Developer Tools', 6 | }; 7 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /website/next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next-sitemap').IConfig} */ 2 | export default { 3 | siteUrl: process.env.SITE_URL || 'https://the-guild.dev/graphql/apollo-angular', 4 | generateIndexSitemap: false, 5 | exclude: ['*/_meta'], 6 | output: 'export', 7 | }; 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Checklist: 2 | 3 | - [ ] If this PR is a new feature, please reference a discussion where a consensus about the design 4 | was reached (not necessary for small changes) 5 | - [ ] Make sure all the significant new logic is covered by tests 6 | -------------------------------------------------------------------------------- /website/src/pages/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | index: { 3 | title: 'Home', 4 | type: 'page', 5 | display: 'hidden', 6 | theme: { 7 | layout: 'raw', 8 | }, 9 | }, 10 | docs: { 11 | title: 'Docs', 12 | type: 'page', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { AppComponent } from './app/app.component'; 3 | import { appConfig } from './app/app.config'; 4 | 5 | bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)); 6 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/install/schema.cts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | /** Name of the project to target. */ 3 | project: string; 4 | /** Url to your GraphQL endpoint */ 5 | endpoint?: string; 6 | /** Version of GraphQL (16 by default) */ 7 | graphql?: string; 8 | } 9 | -------------------------------------------------------------------------------- /website/src/pages/docs/caching/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | configuration: 'Configuration', 3 | interaction: 'Reading and Writing', 4 | 'garbage-collection': 'Garbage Collection', 5 | 'field-behavior': 'Customizing Field Behavior', 6 | 'advanced-topics': 'Advanced Topics', 7 | }; 8 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ApolloTestingController } from './controller'; 2 | export { 3 | ApolloTestingModule, 4 | APOLLO_TESTING_CACHE, 5 | APOLLO_TESTING_NAMED_CACHE, 6 | NamedCaches, 7 | } from './module'; 8 | export { TestOperation, Operation } from './operation'; 9 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/gql.ts: -------------------------------------------------------------------------------- 1 | import { gql as gqlTag, TypedDocumentNode } from '@apollo/client'; 2 | 3 | const typedGQLTag: ( 4 | literals: ReadonlyArray | Readonly, 5 | ...placeholders: any[] 6 | ) => TypedDocumentNode = gqlTag; 7 | 8 | export const gql = typedGQLTag; 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: '/' 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: '*' 10 | update-types: ['version-update:semver-patch', 'version-update:semver-minor'] 11 | -------------------------------------------------------------------------------- /packages/apollo-angular/headers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": ".", 6 | "outDir": "../build/testing", 7 | "paths": { 8 | "apollo-angular": ["../src"] 9 | } 10 | }, 11 | "include": ["src/**/*.ts", "tests/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/apollo-angular/http/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": ".", 6 | "outDir": "../build/testing", 7 | "paths": { 8 | "apollo-angular": ["../src"] 9 | } 10 | }, 11 | "include": ["src/**/*.ts", "tests/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": ".", 6 | "outDir": "../build/testing", 7 | "paths": { 8 | "apollo-angular": ["../src"] 9 | } 10 | }, 11 | "include": ["src/**/*.ts", "tests/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'automatic-persisted-queries': 'Automatic Persisted Queries', 3 | 'angular-cli': 'Angular CLI', 4 | webpack: ' Webpack Loader', 5 | authentication: 'Authentication', 6 | 'multiple-clients': 'Multiple Clients', 7 | nativescript: 'Integrating with NativeScript', 8 | }; 9 | -------------------------------------------------------------------------------- /packages/apollo-angular/persisted-queries/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": ".", 6 | "outDir": "../build/testing", 7 | "paths": { 8 | "apollo-angular": ["../src"] 9 | } 10 | }, 11 | "include": ["src/**/*.ts", "tests/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "build": "ng run demo:build:production", 8 | "start": "ng run demo:serve", 9 | "test": "exit 0" 10 | }, 11 | "peerDependencies": { 12 | "apollo-angular": "*" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /website/src/pages/docs/data/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | queries: 'Queries', 3 | mutations: 'Mutations', 4 | subscriptions: 'Subscriptions', 5 | services: 'Query, Mutation, Subscription Services', 6 | network: 'Network Layer', 7 | pagination: 'Pagination', 8 | fragments: 'Using Fragments', 9 | 'error-handling': 'Error Handling', 10 | }; 11 | -------------------------------------------------------------------------------- /website/src/pages/docs/_meta.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | index: 'Introduction', 3 | 'get-started': 'Get Started', 4 | data: 'Fetching', 5 | caching: 'Caching', 6 | 'local-state': 'Local State', 7 | 'development-and-testing': 'Development and Testing', 8 | performance: 'Performance', 9 | recipes: 'Recipes', 10 | migration: 'Migration Guide', 11 | }; 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Request 4 | url: https://github.com/the-guild-org/apollo-angular/discussions/new 5 | about: Request and discuss a feature 6 | - name: Ask a Question 7 | url: https://github.com/the-guild-org/apollo-angular/discussions/new 8 | about: Ask questions and discuss with other community members 9 | -------------------------------------------------------------------------------- /packages/apollo-angular/scripts/move-schematics.js: -------------------------------------------------------------------------------- 1 | import { cpSync } from 'node:fs'; 2 | 3 | cpSync('schematics/install/files', 'build/schematics/install/files', { recursive: true }); 4 | cpSync('schematics/README.md', 'build/schematics/README.md'); 5 | cpSync('schematics/collection.json', 'build/schematics/collection.json'); 6 | cpSync('schematics/install/schema.json', 'build/schematics/install/schema.json'); 7 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../build/schematics", 5 | "baseUrl": ".", 6 | "rootDir": ".", 7 | "skipDefaultLibCheck": true, 8 | "skipLibCheck": true, 9 | "target": "ES2022", 10 | "module": "commonjs" 11 | }, 12 | "files": ["install/index.cts"], 13 | "include": [] 14 | } 15 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [], 6 | "paths": { 7 | "apollo-angular": ["../apollo-angular/build"], 8 | "apollo-angular/http": ["../apollo-angular/build/http"] 9 | } 10 | }, 11 | "files": ["src/main.ts", "src/polyfills.ts"], 12 | "include": ["src/**/*.d.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@schematics/angular"], 3 | "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", 4 | "schematics": { 5 | "ng-add": { 6 | "description": "Adds Apollo Angular to the application.", 7 | "factory": "./install/index.cjs#factory", 8 | "schema": "./install/schema.json" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.changeset/ 2 | /.devcontainer/ 3 | /packages/apollo-angular/CHANGELOG.md 4 | /packages/apollo-angular/dist/ 5 | /packages/demo/.angular/ 6 | /packages/demo/dist/ 7 | /scripts/ 8 | 9 | # Needed because TextEncoder workaround must happen before importing @angular-devkit/schematics 10 | /packages/apollo-angular/schematics/tests/ng-add.spec.cts 11 | /packages/apollo-angular/schematics/tests/utils.spec.cts 12 | -------------------------------------------------------------------------------- /packages/apollo-angular/tests/vitest.d.ts: -------------------------------------------------------------------------------- 1 | import 'vitest'; 2 | import type { TakeOptions } from '../test-utils/ObservableStream'; 3 | 4 | interface CustomMatchers { 5 | toEmitAnything: (options?: TakeOptions) => Promise; 6 | } 7 | 8 | declare module 'vitest' { 9 | interface Assertion extends CustomMatchers {} 10 | interface AsymmetricMatchersContaining extends CustomMatchers {} 11 | } 12 | -------------------------------------------------------------------------------- /packages/apollo-angular/tests/test-setup.ts: -------------------------------------------------------------------------------- 1 | import '@analogjs/vitest-angular/setup-zone'; 2 | import '../test-utils/matchers'; 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting, 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.rulers": [100], 4 | "files.trimTrailingWhitespace": true, 5 | "files.insertFinalNewline": true, 6 | "files.exclude": { 7 | "**/.git": true, 8 | "**/.DS_Store": true, 9 | "**/node_modules/**": true, 10 | "**/build/**": false, 11 | "**/coverage/**": true, 12 | "**/npm/**": true 13 | }, 14 | "typescript.tsdk": "node_modules/typescript/lib" 15 | } 16 | -------------------------------------------------------------------------------- /packages/apollo-angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": ".", 6 | "outDir": "build", 7 | "inlineSources": true 8 | }, 9 | "include": [ 10 | "headers/**/*.ts", 11 | "http/**/*.ts", 12 | "persisted-queries/**/*.ts", 13 | "src/**/*.ts", 14 | "testing/**/*.ts", 15 | "tests/**/*.ts", 16 | "test-utils/**/*.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/tokens.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import type { ApolloClient } from '@apollo/client'; 3 | import type { Flags, NamedOptions } from './types'; 4 | 5 | export const APOLLO_FLAGS = new InjectionToken('APOLLO_FLAGS'); 6 | 7 | export const APOLLO_OPTIONS = new InjectionToken('APOLLO_OPTIONS'); 8 | 9 | export const APOLLO_NAMED_OPTIONS = new InjectionToken('APOLLO_NAMED_OPTIONS'); 10 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "Ecodev/apollo-angular" }], 4 | "commit": false, 5 | "access": "public", 6 | "baseBranch": "master", 7 | "updateInternalDependencies": "patch", 8 | "ignore": ["website", "demo"], 9 | "snapshot": { 10 | "useCalculatedVersion": true, 11 | "prereleaseTemplate": "{tag}-{datetime}-{commit}" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /website/src/pages/docs/migration.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: Updating your app to Angular Apollo v3. See what's new and how to migrate. 3 | --- 4 | 5 | # Migration Guide to v3 6 | 7 | - File upload requires `extract-files` library 8 | 9 | ## File Upload 10 | 11 | New `apollo-angular` no longer depends on `extract-files` library. To use file uploads, you need to 12 | pass the `extractFiles` function (or create your own). 13 | 14 | See [how to configure file upload](./data/network#file-Upload). 15 | -------------------------------------------------------------------------------- /website/theme.config.tsx: -------------------------------------------------------------------------------- 1 | /* eslint sort-keys: error */ 2 | import { defineConfig, PRODUCTS } from '@theguild/components'; 3 | 4 | export default defineConfig({ 5 | docsRepositoryBase: 'https://github.com/the-guild-org/apollo-angular/tree/master/website', 6 | main({ children }) { 7 | return <>{children}; 8 | }, 9 | websiteName: 'Apollo Angular', 10 | description: 'A fully-featured GraphQL client for Angular', 11 | logo: PRODUCTS.ANGULAR.logo({ className: 'w-8' }), 12 | }); 13 | -------------------------------------------------------------------------------- /packages/demo/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterLink, RouterOutlet } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | template: ` 7 |
8 |
9 |

Star Wars

10 |
11 | 12 |
13 | `, 14 | standalone: true, 15 | imports: [RouterOutlet, RouterLink], 16 | }) 17 | export class AppComponent {} 18 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-angular/testing", 3 | "type": "module", 4 | "description": "A testing backend for Apollo", 5 | "contributors": [ 6 | { 7 | "name": "Kamil Kisiela", 8 | "email": "kamil.kisiela@gmail.com", 9 | "url": "https://github.com/kamilkisiela/" 10 | } 11 | ], 12 | "license": "MIT", 13 | "module": "../build/fesm2020/ngApolloTesting.mjs", 14 | "typings": "../build/testing/index.d.ts", 15 | "sideEffects": false 16 | } 17 | -------------------------------------------------------------------------------- /packages/demo/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { MoviePageComponent } from './pages/movie/movie-page.component'; 3 | import { MoviesPageComponent } from './pages/movies/movies-page.component'; 4 | 5 | export const routes: Routes = [ 6 | { 7 | path: 'movie', 8 | component: MoviesPageComponent, 9 | }, 10 | { 11 | path: 'movie/:id', 12 | component: MoviePageComponent, 13 | }, 14 | { 15 | path: '**', 16 | redirectTo: '/movie', 17 | }, 18 | ]; 19 | -------------------------------------------------------------------------------- /packages/apollo-angular/http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-angular/http", 3 | "type": "module", 4 | "description": "An Apollo Link to allow sending an http request per operation.", 5 | "contributors": [ 6 | { 7 | "name": "Kamil Kisiela", 8 | "email": "kamil.kisiela@gmail.com", 9 | "url": "https://github.com/kamilkisiela/" 10 | } 11 | ], 12 | "license": "MIT", 13 | "module": "../build/fesm2020/ngApolloLinkHttp.mjs", 14 | "typings": "../build/http/index.d.ts", 15 | "sideEffects": false 16 | } 17 | -------------------------------------------------------------------------------- /.devcontainer/library-scripts/README.md: -------------------------------------------------------------------------------- 1 | # Warning: Folder contents may be replaced 2 | 3 | The contents of this folder will be automatically replaced with a file of the same name in the 4 | [vscode-dev-containers](https://github.com/microsoft/vscode-dev-containers) repository's 5 | [script-library folder](https://github.com/microsoft/vscode-dev-containers/tree/master/script-library) 6 | whenever the repository is packaged. 7 | 8 | To retain your edits, move the file to a different location. You may also delete the files if they 9 | are not needed. 10 | -------------------------------------------------------------------------------- /packages/apollo-angular/headers/src/index.ts: -------------------------------------------------------------------------------- 1 | import { HttpHeaders } from '@angular/common/http'; 2 | import { ApolloLink } from '@apollo/client'; 3 | 4 | export class HttpHeadersLink extends ApolloLink { 5 | constructor() { 6 | super((operation, forward) => { 7 | const { getContext, setContext } = operation; 8 | const context = getContext(); 9 | 10 | if (context.headers) { 11 | setContext({ headers: new HttpHeaders(context.headers) }); 12 | } 13 | 14 | return forward(operation); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/apollo-angular/persisted-queries/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-angular/persisted-queries", 3 | "type": "module", 4 | "description": "Use persisted queries with Apollo Link", 5 | "contributors": [ 6 | { 7 | "name": "Kamil Kisiela", 8 | "email": "kamil.kisiela@gmail.com", 9 | "url": "https://github.com/kamilkisiela/" 10 | } 11 | ], 12 | "license": "MIT", 13 | "module": "../build/fesm2020/ngApolloLinkPersisted.mjs", 14 | "typings": "../build/persisted-queries/index.d.ts", 15 | "sideEffects": false 16 | } 17 | -------------------------------------------------------------------------------- /packages/apollo-angular/persisted-queries/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ### vNext 4 | 5 | ### v1.3.0 6 | 7 | - Support Angular 10 8 | 9 | ### v1.2.0 10 | 11 | - Bump version ranges for GraphQL and Apollo Link 12 | 13 | ### v1.1.0 14 | 15 | - Introduces `useGETForHashedQueries` 16 | 17 | ### v1.0.0 18 | 19 | - Uses `ng-packagr` 20 | 21 | ### v1.0.0-beta.2 22 | 23 | - Adds `sideEffects: false` (webpack) 24 | ([PR #580](https://github.com/the-guild-org/apollo-angular/pull/580)) 25 | 26 | ### v1.0.0-beta.0 27 | 28 | Initial release. We didn't track changes before this version. 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | stable: 9 | permissions: 10 | contents: write 11 | id-token: write 12 | packages: write 13 | issues: write 14 | pull-requests: write 15 | uses: the-guild-org/shared-config/.github/workflows/release-stable.yml@main 16 | with: 17 | createGithubReleases: true 18 | releaseScript: release 19 | nodeVersion: 22 20 | secrets: 21 | githubToken: ${{ secrets.GITHUB_TOKEN }} 22 | npmToken: ${{ secrets.NPM_TOKEN }} 23 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/README.md: -------------------------------------------------------------------------------- 1 | # Apollo Angular Schematics 2 | 3 | A collection of Schematics for Apollo Angular. 4 | 5 | ## Collection 6 | 7 | ### ng add 8 | 9 | Add Apollo Angular and its dependencies and configures the application. 10 | 11 | - Adds the following packages as dependencies to `package.json`: 12 | - apollo-angular 13 | - @apollo/client 14 | - graphql 15 | - Adds `GraphQLModule` with required setup to use the plugin. 16 | - Imports `GraphQLModule` in the root NgModule (`AppModule`). 17 | - Imports `HttpClientModule` in the root NgModule (`AppModule`). 18 | 19 | Command: `ng add apollo-angular` 20 | -------------------------------------------------------------------------------- /packages/apollo-angular/headers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-angular/headers", 3 | "type": "module", 4 | "description": "An Apollo Link to easily transform headers from being a key-value object to an instance of HttpHeaders. Great combination with apollo-angular-link-http.", 5 | "contributors": [ 6 | { 7 | "name": "Kamil Kisiela", 8 | "email": "kamil.kisiela@gmail.com", 9 | "url": "https://github.com/kamilkisiela/" 10 | } 11 | ], 12 | "license": "MIT", 13 | "module": "../build/fesm2020/ngApolloLinkHeaders.mjs", 14 | "typings": "../build/headers/index.d.ts", 15 | "sideEffects": false 16 | } 17 | -------------------------------------------------------------------------------- /packages/apollo-angular/http/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { ApolloClient, ApolloLink, execute, InMemoryCache } from '@apollo/client'; 3 | 4 | export function createDefaultExecuteContext(): ApolloLink.ExecuteContext { 5 | return { 6 | client: new ApolloClient({ 7 | cache: new InMemoryCache(), 8 | link: ApolloLink.empty(), 9 | }), 10 | }; 11 | } 12 | 13 | export function executeWithDefaultContext( 14 | link: ApolloLink, 15 | request: ApolloLink.Request, 16 | context: ApolloLink.ExecuteContext = createDefaultExecuteContext(), 17 | ): Observable { 18 | return execute(link, request, context); 19 | } 20 | -------------------------------------------------------------------------------- /packages/demo/src/styles.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: 4 | -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 5 | 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 12 | } 13 | 14 | header { 15 | background-color: #eeeeee; 16 | display: flex; 17 | flex-direction: row; 18 | align-items: center; 19 | justify-content: space-between; 20 | font-size: calc(10px + 2vmin); 21 | color: black; 22 | padding: 20px; 23 | } 24 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { TypedDocumentNode } from '@apollo/client'; 2 | export { provideApollo, provideNamedApollo } from './apollo-module'; 3 | export { Apollo, ApolloBase } from './apollo'; 4 | export { QueryRef, QueryRefFromDocument } from './query-ref'; 5 | export { Query } from './query'; 6 | export { Mutation } from './mutation'; 7 | export { Subscription } from './subscription'; 8 | export { APOLLO_OPTIONS, APOLLO_NAMED_OPTIONS, APOLLO_FLAGS } from './tokens'; 9 | export type { Flags, NamedOptions, ResultOf, VariablesOf } from './types'; 10 | export { gql } from './gql'; 11 | export { onlyCompleteData, onlyComplete, onlyCompleteFragment } from './only-complete-data'; 12 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VARIANT=22 2 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} 3 | 4 | # [Optional] Uncomment this section to install additional OS packages. 5 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 6 | # && apt-get -y install --no-install-recommends 7 | 8 | # [Optional] Uncomment if you want to install an additional version of node using nvm 9 | # ARG EXTRA_NODE_VERSION=10 10 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 11 | 12 | # [Optional] Uncomment if you want to install more global node modules 13 | # RUN sudo -u node npm install -g 14 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/install/files/module/graphql.module.ts: -------------------------------------------------------------------------------- 1 | import { provideApollo } from 'apollo-angular'; 2 | import { HttpLink } from 'apollo-angular/http'; 3 | import { inject, NgModule } from '@angular/core'; 4 | import { ApolloClient, InMemoryCache } from '@apollo/client'; 5 | 6 | export function createApollo(): ApolloClient.Options { 7 | const uri = '<%= endpoint %>'; // <-- add the URL of the GraphQL server here 8 | const httpLink = inject(HttpLink); 9 | 10 | return { 11 | link: httpLink.create({ uri }), 12 | cache: new InMemoryCache(), 13 | }; 14 | } 15 | 16 | @NgModule({ 17 | providers: [provideApollo(createApollo)], 18 | }) 19 | export class GraphQLModule {} 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "module": "preserve", 6 | "importHelpers": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "outDir": "build", 10 | "experimentalDecorators": true, 11 | "noImplicitOverride": true, 12 | "noImplicitReturns": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "strict": true 16 | }, 17 | "angularCompilerOptions": { 18 | "strictInjectionParameters": true, 19 | "strictInputAccessModifiers": true, 20 | "typeCheckHostBindings": true, 21 | "strictTemplates": true, 22 | "strictMetadataEmit": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "lib": ["dom", "dom.iterable", "esnext"], 10 | "allowJs": true, 11 | "noEmit": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "paths": { 18 | "@/*": ["./*"], 19 | "public/*": ["./public/*"] 20 | } 21 | }, 22 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /packages/apollo-angular/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | import viteTsConfigPaths from 'vite-tsconfig-paths'; 4 | import angular from '@analogjs/vite-plugin-angular'; 5 | 6 | export default defineConfig(({ mode }) => ({ 7 | plugins: [angular(), viteTsConfigPaths()], 8 | test: { 9 | globals: true, 10 | setupFiles: ['tests/test-setup.ts'], 11 | environment: 'jsdom', 12 | include: [ 13 | '{headers,http,persisted-queries,src,testing,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', 14 | ], 15 | reporters: ['default'], 16 | }, 17 | define: { 18 | 'import.meta.vitest': mode !== 'production', 19 | }, 20 | })); 21 | -------------------------------------------------------------------------------- /website/src/pages/docs/caching/interaction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to efficiently read and write data to the cache with Apollo Client. Check out the 4 | "Reading and writing data to the cache" chapter in the Apollo Client documentation. 5 | --- 6 | 7 | # Reading and Writing Data to the Cache 8 | 9 | Apollo Client provides three methods for reading and writing data to the cache: 10 | 11 | - `readQuery` and `readFragment` 12 | - `writeQuery` and `writeFragment` 13 | - `cache.modify` (a method of `InMemoryCache`) 14 | 15 | Please read the 16 | ["Reading and writing data to the cache"](https://apollographql.com/docs/react/caching/cache-interaction) 17 | chapter on Apollo Client documentation. 18 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { ApolloClient, TypedDocumentNode } from '@apollo/client'; 2 | 3 | export type EmptyObject = { 4 | [key: string]: any; 5 | }; 6 | 7 | export type ResultOf = 8 | T extends TypedDocumentNode ? R : never; 9 | export type VariablesOf = 10 | T extends TypedDocumentNode ? V : never; 11 | 12 | export type Omit = Pick>; 13 | 14 | export type NamedOptions = Record; 15 | 16 | export type Flags = { 17 | /** 18 | * Observable starts with `{ loading: true }`. 19 | * 20 | * Disabled by default 21 | */ 22 | useMutationLoading?: boolean; 23 | }; 24 | -------------------------------------------------------------------------------- /scripts/run-e2e-locally.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -xe 4 | 5 | yarn workspace apollo-angular build 6 | (cd packages/apollo-angular/build && yarn pack --filename apollo-angular.tgz && mv apollo-angular.tgz ../apollo-angular.tgz) 7 | yarn cache clean apollo-angular 8 | rm -rf testapp 9 | ng new testapp --package-manager yarn --defaults --minimal --skip-git 10 | (cd testapp && ng add ../packages/apollo-angular/apollo-angular.tgz --graphql '16.0.0' --defaults --verbose --skip-confirmation) 11 | (cd testapp && yarn ng run testapp:build:production) 12 | (cd testapp && ng add @cypress/schematic --defaults --verbose --skip-confirmation) 13 | ./scripts/prepare-e2e.js testapp 16 14 | (cd testapp && yarn ng run testapp:cypress-run:production) 15 | rm -rf testapp 16 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/install/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "SchematicsApolloAngularInstall", 4 | "title": "Apollo Angular Install Options Schema", 5 | "type": "object", 6 | "properties": { 7 | "project": { 8 | "type": "string", 9 | "description": "The name of the project.", 10 | "$default": { 11 | "$source": "projectName" 12 | } 13 | }, 14 | "endpoint": { 15 | "type": "string", 16 | "minLength": 0, 17 | "default": "", 18 | "x-prompt": "Url to your GraphQL API" 19 | }, 20 | "graphql": { 21 | "type": "string", 22 | "minLength": 0, 23 | "default": "16", 24 | "x-prompt": "Version of GraphQL" 25 | } 26 | }, 27 | "required": [] 28 | } 29 | -------------------------------------------------------------------------------- /packages/apollo-angular/persisted-queries/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink } from '@apollo/client/link'; 2 | import { SetContextLink } from '@apollo/client/link/context'; 3 | import { PersistedQueryLink } from '@apollo/client/link/persisted-queries'; 4 | 5 | export type Options = PersistedQueryLink.Options; 6 | 7 | const transformLink = new SetContextLink(context => { 8 | const ctx: any = {}; 9 | 10 | if (context.http) { 11 | ctx.includeQuery = context.http.includeQuery; 12 | ctx.includeExtensions = context.http.includeExtensions; 13 | } 14 | 15 | if (context.fetchOptions && context.fetchOptions.method) { 16 | ctx.method = context.fetchOptions.method; 17 | } 18 | 19 | return ctx; 20 | }); 21 | 22 | export const createPersistedQueryLink = (options: PersistedQueryLink.Options) => 23 | ApolloLink.from([new PersistedQueryLink(options), transformLink]); 24 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/tests/utils.spec.cts: -------------------------------------------------------------------------------- 1 | import { TextDecoder, TextEncoder } from 'util'; 2 | global.TextEncoder = TextEncoder; 3 | global.TextDecoder = TextDecoder as any; 4 | 5 | import { parseJSON } from '../utils/index.cjs'; 6 | 7 | it('support // comments', () => { 8 | expect( 9 | parseJSON( 10 | 'file.json', 11 | ` 12 | { 13 | "foo": { 14 | // "baz": true, 15 | "bar": true 16 | } 17 | } 18 | `, 19 | ), 20 | ).toEqual({ 21 | foo: { 22 | bar: true, 23 | }, 24 | }); 25 | }); 26 | 27 | it('support /* */ comments', () => { 28 | expect( 29 | parseJSON( 30 | 'file.json', 31 | ` 32 | { 33 | "foo": { 34 | /* "baz": true, */ 35 | "bar": true 36 | } 37 | } 38 | `, 39 | ), 40 | ).toEqual({ 41 | foo: { 42 | bar: true, 43 | }, 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/apollo-angular/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import * as api from '../src'; 3 | import { Apollo } from '../src/apollo'; 4 | import { provideApollo, provideNamedApollo } from '../src/apollo-module'; 5 | import { gql } from '../src/gql'; 6 | import { QueryRef } from '../src/query-ref'; 7 | 8 | describe('public api', () => { 9 | test('should export Apollo', () => { 10 | expect(api.Apollo).toBe(Apollo); 11 | }); 12 | test('should export QueryRef', () => { 13 | expect(api.QueryRef).toBe(QueryRef); 14 | }); 15 | test('should export provideApollo', () => { 16 | expect(api.provideApollo).toBe(provideApollo); 17 | }); 18 | test('should export provideNamedApollo', () => { 19 | expect(api.provideNamedApollo).toBe(provideNamedApollo); 20 | }); 21 | test('should export gql', () => { 22 | expect(api.gql).toBe(gql); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve 4 | --- 5 | 6 | **Describe the bug** 7 | 8 | 9 | 10 | **To Reproduce** 11 | 12 | Steps to reproduce the behavior: 13 | 14 | **Expected behavior** 15 | 16 | 17 | 18 | **Environment:** 19 | 20 | 27 | 28 | ``` 29 | - @angular/cli@XX.YY.ZZ 30 | - @angular/core@XX.YY.ZZ 31 | - @apollo/client@XX.YY.ZZ 32 | - apollo-angular@XX.YY.ZZ 33 | - graphql@XX.YY.ZZ 34 | - typescript@XX.YY.ZZ 35 | ``` 36 | 37 | **Additional context** 38 | 39 | 40 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node.js", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | // Update 'VARIANT' to pick a Node version: 10, 12, 14 6 | "args": { "VARIANT": "14" } 7 | }, 8 | 9 | // Set *default* container specific settings.json values on container create. 10 | "settings": { 11 | "terminal.integrated.shell.linux": "/bin/bash" 12 | }, 13 | 14 | // Add the IDs of extensions you want installed when the container is created. 15 | "extensions": ["dbaeumer.vscode-eslint"] 16 | 17 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 18 | // "forwardPorts": [], 19 | 20 | // Use 'postCreateCommand' to run commands after the container is created. 21 | // "postCreateCommand": "yarn install", 22 | 23 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. 24 | // "remoteUser": "node" 25 | } 26 | -------------------------------------------------------------------------------- /packages/demo/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { provideApollo } from 'apollo-angular'; 2 | import { HttpLink } from 'apollo-angular/http'; 3 | import { provideHttpClient } from '@angular/common/http'; 4 | import { ApplicationConfig, inject, provideZoneChangeDetection } from '@angular/core'; 5 | import { provideRouter } from '@angular/router'; 6 | import { InMemoryCache } from '@apollo/client'; 7 | import { routes } from './app.routes'; 8 | 9 | export const appConfig: ApplicationConfig = { 10 | providers: [ 11 | provideZoneChangeDetection({ eventCoalescing: true }), 12 | provideRouter(routes), 13 | provideHttpClient(), 14 | provideApollo(() => { 15 | const httpLink = inject(HttpLink); 16 | 17 | return { 18 | link: httpLink.create({ 19 | uri: 'https://swapi-graphql.netlify.app/graphql', 20 | }), 21 | cache: new InMemoryCache(), 22 | }; 23 | }), 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "1.0.0", 4 | "type": "module", 5 | "private": true, 6 | "scripts": { 7 | "build": "next build && next-sitemap", 8 | "dev": "next", 9 | "start": "next start", 10 | "test": "echo 'nothing to do here'" 11 | }, 12 | "dependencies": { 13 | "@theguild/components": "^6.5.3", 14 | "next": "^14.2.3", 15 | "next-sitemap": "^4.2.3", 16 | "react": "^18.3.1", 17 | "react-dom": "^18.3.1" 18 | }, 19 | "devDependencies": { 20 | "@theguild/tailwind-config": "0.4.2", 21 | "@types/node": "20.12.12", 22 | "@types/react": "18.3.3", 23 | "typescript": "5.4.5" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/angular-cli.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to integrate Apollo with Angular using ng add apollo-angular. Use Angular CLI Proxy to 4 | define configurations for your GraphQL endpoint 5 | --- 6 | 7 | # Angular CLI 8 | 9 | To get started with Apollo and Angular run: 10 | 11 | ```sh 12 | ng add apollo-angular 13 | ``` 14 | 15 | ## Proxy 16 | 17 | If your GraphQL endpoint lives under different host with Angular CLI you can easily define proxy 18 | configuration. 19 | 20 | Take for example `api.example.com/graphql`: 21 | 22 | ```json 23 | { 24 | "/graphql": { 25 | "target": "http://api.example.com" 26 | } 27 | } 28 | ``` 29 | 30 | Create a json file (`proxy.config.json` for example) with that configuration. 31 | 32 | To run server use `--proxy-config` option: 33 | 34 | ```sh 35 | ng serve --proxy-config 36 | ``` 37 | 38 | An example: 39 | 40 | ```sh 41 | ng serve --proxy-config proxy.config.json 42 | ``` 43 | -------------------------------------------------------------------------------- /website/src/pages/docs/caching/field-behavior.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to customize cached fields in Apollo Client by defining field policies with read and 4 | merge functions. Optimize your cache with key arguments. Read more at the Apollo Client 5 | documentation. 6 | --- 7 | 8 | # Customizing the Behavior of Cached Fields 9 | 10 | You can customize how a particular field in your Apollo Client cache is read and written. To do so, 11 | you define a **field policy** for the field. A field policy can include: 12 | 13 | - A `read` function that specifies what happens when the field's cached value is read 14 | - A `merge` function that specifies what happens when field's cached value is written 15 | - An array of key arguments that help the cache avoid storing unnecessary duplicate data. 16 | 17 | Please read the 18 | ["Customizing the behavior of cached fields"](https://www.apollographql.com/docs/react/caching/cache-field-behavior/) 19 | chapter on Apollo Client documentation. 20 | -------------------------------------------------------------------------------- /packages/apollo-angular/tests/integration.spec.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, test } from 'vitest'; 2 | import { provideHttpClient } from '@angular/common/http'; 3 | import { TestBed } from '@angular/core/testing'; 4 | import { InMemoryCache } from '@apollo/client'; 5 | import { MockLink } from '@apollo/client/testing'; 6 | import { Apollo, provideApollo } from '../src'; 7 | 8 | describe('Integration', () => { 9 | describe('default', () => { 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | providers: [ 13 | provideHttpClient(), 14 | provideApollo(() => { 15 | return { 16 | link: new MockLink([]), 17 | cache: new InMemoryCache(), 18 | }; 19 | }), 20 | ], 21 | }); 22 | }); 23 | 24 | test('apollo should be initialized', () => { 25 | const apollo = TestBed.inject(Apollo); 26 | expect(() => apollo.client).not.toThrow(); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /website/src/pages/docs/caching/garbage-collection.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to manage cached data in Apollo Client 3 with garbage collection and cache eviction 4 | strategies, and gain fine-grained control with the evict method. Read more on the documentation. 5 | --- 6 | 7 | import { Callout } from '@theguild/components'; 8 | 9 | # Garbage Collection and Cache Eviction 10 | 11 | Apollo Client 3 enables you to selectively remove cached data that is no longer useful. The default 12 | garbage collection strategy of the `gc` method is suitable for most applications, but the `evict` 13 | method provides more fine-grained control for applications that require it. 14 | 15 | 16 | You call these methods directly on the `InMemoryCache` object, not on the `ApolloClient` object. 17 | 18 | 19 | Please read the 20 | ["Garbage collection and cache eviction"](https://apollographql.com/docs/react/caching/garbage-collection) 21 | chapter on Apollo Client documentation. 22 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/apollo-module.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@angular/core'; 2 | import { ApolloClient } from '@apollo/client'; 3 | import { Apollo } from './apollo'; 4 | import { APOLLO_FLAGS, APOLLO_NAMED_OPTIONS, APOLLO_OPTIONS } from './tokens'; 5 | import { Flags, NamedOptions } from './types'; 6 | 7 | export function provideApollo( 8 | optionsFactory: () => ApolloClient.Options, 9 | flags: Flags = {}, 10 | ): Provider { 11 | return [ 12 | Apollo, 13 | { 14 | provide: APOLLO_OPTIONS, 15 | useFactory: optionsFactory, 16 | }, 17 | { 18 | provide: APOLLO_FLAGS, 19 | useValue: flags, 20 | }, 21 | ]; 22 | } 23 | 24 | export function provideNamedApollo( 25 | optionsFactory: () => NamedOptions, 26 | flags: Flags = {}, 27 | ): Provider { 28 | return [ 29 | Apollo, 30 | { 31 | provide: APOLLO_NAMED_OPTIONS, 32 | useFactory: optionsFactory, 33 | }, 34 | { 35 | provide: APOLLO_FLAGS, 36 | useValue: flags, 37 | }, 38 | ]; 39 | } 40 | -------------------------------------------------------------------------------- /website/src/pages/docs/local-state/reactive-variables.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn about reactive variables in Apollo Client 3, a useful mechanism for storing local state 4 | outside the cache. Modify them anywhere & trigger updates in active queries. 5 | --- 6 | 7 | # Reactive Variables 8 | 9 | New in Apollo Client 3, reactive variables are a useful mechanism for storing local state outside 10 | the Apollo Client cache. Because they're separate from the cache, reactive variables can store data 11 | of any type and structure, and you can interact with them anywhere in your application without using 12 | GraphQL syntax. 13 | 14 | Most importantly, modifying a reactive variable automatically triggers an update of every active 15 | query that depends on that variable. A query depends on a reactive variable if any of the query's 16 | requested fields defines a read function that reads the variable's value. 17 | 18 | Please read the 19 | ["Reactive variables"](https://apollographql.com/docs/react/local-state/reactive-variables) chapter 20 | on Apollo Client documentation. 21 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/tests/utils.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from 'graphql'; 2 | import { Observable } from 'rxjs'; 3 | import { ApolloClient, execute, InMemoryCache, OperationVariables } from '@apollo/client'; 4 | import { ApolloLink } from '@apollo/client/link'; 5 | import { addTypenameToDocument } from '@apollo/client/utilities'; 6 | 7 | export function buildOperationForLink( 8 | document: DocumentNode, 9 | variables: OperationVariables | undefined, 10 | ): ApolloLink.Request { 11 | return { 12 | query: addTypenameToDocument(document), 13 | variables, 14 | context: {}, 15 | }; 16 | } 17 | 18 | export function createDefaultExecuteContext(): ApolloLink.ExecuteContext { 19 | return { 20 | client: new ApolloClient({ 21 | cache: new InMemoryCache(), 22 | link: ApolloLink.empty(), 23 | }), 24 | }; 25 | } 26 | 27 | export function executeWithDefaultContext( 28 | link: ApolloLink, 29 | request: ApolloLink.Request, 30 | context: ApolloLink.ExecuteContext = createDefaultExecuteContext(), 31 | ): Observable { 32 | return execute(link, request, context); 33 | } 34 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "compileOnSave": false, 5 | "compilerOptions": { 6 | "outDir": "./dist/out-tsc", 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "skipLibCheck": true, 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "sourceMap": true, 16 | "declaration": false, 17 | "experimentalDecorators": true, 18 | "moduleResolution": "bundler", 19 | "importHelpers": true, 20 | "target": "ES2022", 21 | "module": "ES2022", 22 | "lib": ["ES2022", "dom"] 23 | }, 24 | "angularCompilerOptions": { 25 | "enableI18nLegacyMessageIdFormat": false, 26 | "strictInjectionParameters": true, 27 | "strictInputAccessModifiers": true, 28 | "strictTemplates": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2020 Kamil Kisiela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/apollo-angular/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2020 Kamil Kisiela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/apollo-angular/headers/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2020 Kamil Kisiela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/apollo-angular/http/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2020 Kamil Kisiela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/apollo-angular/persisted-queries/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2020 Kamil Kisiela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/utils/test.cts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; 3 | 4 | const collectionPath = join(__dirname, '../collection.json'); 5 | 6 | async function createTestApp(appOptions = {}): Promise { 7 | const runner = new SchematicTestRunner('apollo-angular', collectionPath); 8 | 9 | const workspaceTree = await runner.runExternalSchematic('@schematics/angular', 'workspace', { 10 | name: 'workspace', 11 | version: '11.0.0', 12 | newProjectRoot: 'projects', 13 | }); 14 | 15 | return runner.runExternalSchematic( 16 | '@schematics/angular', 17 | 'application', 18 | { 19 | ...appOptions, 20 | name: 'apollo', 21 | }, 22 | workspaceTree, 23 | ); 24 | } 25 | 26 | export async function runNgAdd(standalone: boolean): Promise { 27 | const projectName = 'apollo'; 28 | const appTree = await createTestApp({ standalone: standalone, name: projectName }); 29 | 30 | const runner = new SchematicTestRunner('apollo-angular', collectionPath); 31 | 32 | return await runner.runSchematic('ng-add', { project: projectName }, appTree); 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | lerna-debug.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | *.tgz 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | packages/*/coverage 19 | packages/**/coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # node-waf configuration 28 | .lock-wscript 29 | 30 | # Compiled binary addons (http://nodejs.org/api/addons.html) 31 | packages/*/build/ 32 | 33 | # Dependency directories 34 | packages/*/node_modules 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional eslint cache 42 | .eslintcache 43 | 44 | # Optional REPL history 45 | .node_repl_history 46 | 47 | # IDEs 48 | .idea/ 49 | 50 | # Packages lock 51 | package-lock.json 52 | 53 | # npm directories (for deploying) 54 | packages/*/npm/ 55 | 56 | # docs 57 | docs/public/docs 58 | 59 | .next/ 60 | website/public/sitemap.xml 61 | website/public/_redirects 62 | website/out/ 63 | .angular/ 64 | dist/ 65 | -------------------------------------------------------------------------------- /scripts/bump.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /// @ts-check 4 | const fs = require('fs'); 5 | const globby = require('globby'); 6 | const { resolve } = require('path'); 7 | const { execSync } = require('child_process'); 8 | 9 | async function main() { 10 | const rootPkg = readJSON(resolve(__dirname, '../package.json')); 11 | const packageDirs = await globby(rootPkg.workspaces, { 12 | cwd: process.cwd(), 13 | onlyDirectories: true, 14 | }); 15 | 16 | packageDirs.forEach(bumpAlpha); 17 | } 18 | 19 | /** 20 | * @param {string} packageDir 21 | */ 22 | function bumpAlpha(packageDir) { 23 | const filepath = resolve(packageDir, 'package.json'); 24 | const pkg = readJSON(filepath); 25 | 26 | const name = pkg.name; 27 | const version = pkg.version.replace(/-alpha\.(\d+)/, (_, ver) => { 28 | return `-alpha.${parseInt(ver, 10) + 1}`; 29 | }); 30 | 31 | execSync(`./scripts/version.js ${name} ${version}`, { 32 | stdio: 'inherit', 33 | }); 34 | } 35 | 36 | /** 37 | * @param {string} filepath 38 | */ 39 | function readFile(filepath) { 40 | return fs.readFileSync(resolve(__dirname, filepath), { 41 | encoding: 'utf-8', 42 | }); 43 | } 44 | 45 | /** 46 | * @param {string} filepath 47 | */ 48 | function readJSON(filepath) { 49 | return JSON.parse(readFile(filepath)); 50 | } 51 | 52 | main(); 53 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: pr 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | dependencies: 9 | uses: the-guild-org/shared-config/.github/workflows/changesets-dependencies.yaml@main 10 | secrets: 11 | githubToken: ${{ secrets.GITHUB_TOKEN }} 12 | 13 | alpha: 14 | permissions: 15 | contents: write 16 | id-token: write 17 | packages: write 18 | uses: the-guild-org/shared-config/.github/workflows/release-snapshot.yml@main 19 | if: ${{ github.event.pull_request.title != 'Upcoming Release Changes' }} 20 | with: 21 | npmTag: alpha 22 | buildScript: build 23 | nodeVersion: 22 24 | secrets: 25 | githubToken: ${{ secrets.GITHUB_TOKEN }} 26 | npmToken: ${{ secrets.NPM_TOKEN }} 27 | 28 | release-candidate: 29 | permissions: 30 | contents: write 31 | id-token: write 32 | packages: write 33 | uses: the-guild-org/shared-config/.github/workflows/release-snapshot.yml@main 34 | if: ${{ github.event.pull_request.title == 'Upcoming Release Changes' }} 35 | with: 36 | npmTag: rc 37 | restoreDeletedChangesets: true 38 | buildScript: build 39 | nodeVersion: 22 40 | secrets: 41 | githubToken: ${{ secrets.GITHUB_TOKEN }} 42 | npmToken: ${{ secrets.NPM_TOKEN }} 43 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/mutation.ts: -------------------------------------------------------------------------------- 1 | import type { DocumentNode } from 'graphql'; 2 | import type { Observable } from 'rxjs'; 3 | import { Injectable } from '@angular/core'; 4 | import type { OperationVariables, TypedDocumentNode } from '@apollo/client'; 5 | import { Apollo } from './apollo'; 6 | import type { EmptyObject } from './types'; 7 | 8 | export declare namespace Mutation { 9 | export type MutateOptions< 10 | TData = unknown, 11 | TVariables extends OperationVariables = EmptyObject, 12 | > = Omit, 'mutation'>; 13 | } 14 | 15 | @Injectable() 16 | export abstract class Mutation< 17 | TData = unknown, 18 | TVariables extends OperationVariables = EmptyObject, 19 | > { 20 | public abstract readonly document: DocumentNode | TypedDocumentNode; 21 | public client = 'default'; 22 | 23 | constructor(protected readonly apollo: Apollo) {} 24 | 25 | public mutate( 26 | ...[options]: {} extends TVariables 27 | ? [options?: Mutation.MutateOptions] 28 | : [options: Mutation.MutateOptions] 29 | ): Observable> { 30 | return this.apollo.use(this.client).mutate({ 31 | ...options, 32 | mutation: this.document, 33 | } as Apollo.MutateOptions); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/src/operation.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLFormattedError, OperationTypeNode } from 'graphql'; 2 | import { Observer } from 'rxjs'; 3 | import { ApolloLink, ErrorLike } from '@apollo/client'; 4 | import { isErrorLike } from '@apollo/client/errors'; 5 | 6 | export type Operation = ApolloLink.Operation & { 7 | clientName: string; 8 | }; 9 | 10 | export class TestOperation { 11 | constructor( 12 | public readonly operation: Operation, 13 | private readonly observer: Observer>, 14 | ) {} 15 | 16 | public flush(result: ApolloLink.Result | ErrorLike): void { 17 | if (isErrorLike(result)) { 18 | this.observer.error(result); 19 | } else { 20 | const fetchResult = result ? { ...result } : result; 21 | this.observer.next(fetchResult); 22 | 23 | if (this.operation.operationType !== OperationTypeNode.SUBSCRIPTION) { 24 | this.complete(); 25 | } 26 | } 27 | } 28 | 29 | public complete() { 30 | this.observer.complete(); 31 | } 32 | 33 | public flushData(data: T | null): void { 34 | this.flush({ data }); 35 | } 36 | 37 | public networkError(error: ErrorLike): void { 38 | this.flush(error); 39 | } 40 | 41 | public graphqlErrors(errors: GraphQLFormattedError[]): void { 42 | this.flush({ 43 | errors, 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/subscription.ts: -------------------------------------------------------------------------------- 1 | import type { DocumentNode } from 'graphql'; 2 | import type { Observable } from 'rxjs'; 3 | import { Injectable } from '@angular/core'; 4 | import type { OperationVariables, TypedDocumentNode } from '@apollo/client'; 5 | import { Apollo } from './apollo'; 6 | import { EmptyObject } from './types'; 7 | 8 | export declare namespace Subscription { 9 | export type SubscribeOptions< 10 | TData = unknown, 11 | TVariables extends OperationVariables = EmptyObject, 12 | > = Omit, 'query'>; 13 | } 14 | 15 | @Injectable() 16 | export abstract class Subscription< 17 | TData = unknown, 18 | TVariables extends OperationVariables = EmptyObject, 19 | > { 20 | public abstract readonly document: DocumentNode | TypedDocumentNode; 21 | public client = 'default'; 22 | 23 | constructor(protected readonly apollo: Apollo) {} 24 | 25 | public subscribe( 26 | ...[options]: {} extends TVariables 27 | ? [options?: Subscription.SubscribeOptions] 28 | : [options: Subscription.SubscribeOptions] 29 | ): Observable> { 30 | return this.apollo.use(this.client).subscribe({ 31 | ...options, 32 | query: this.document, 33 | } as Apollo.SubscribeOptions); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/apollo-angular/http/src/types.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from 'graphql'; 2 | import { HttpContext, HttpHeaders } from '@angular/common/http'; 3 | import { ApolloLink } from '@apollo/client'; 4 | 5 | declare module '@apollo/client' { 6 | export interface DefaultContext extends Context {} 7 | } 8 | 9 | export type HttpRequestOptions = { 10 | headers?: HttpHeaders; 11 | withCredentials?: boolean; 12 | useMultipart?: boolean; 13 | httpContext?: HttpContext; 14 | }; 15 | 16 | export type RequestOptions = Omit & { 17 | context?: HttpContext; 18 | }; 19 | 20 | export type URIFunction = (operation: ApolloLink.Operation) => string; 21 | 22 | export type FetchOptions = { 23 | method?: string; 24 | uri?: string | URIFunction; 25 | includeExtensions?: boolean; 26 | includeQuery?: boolean; 27 | }; 28 | 29 | export type OperationPrinter = (operation: DocumentNode) => string; 30 | 31 | export type Body = { 32 | query?: string; 33 | variables?: Record; 34 | operationName?: string; 35 | extensions?: Record; 36 | }; 37 | 38 | export interface Context extends FetchOptions, HttpRequestOptions {} 39 | 40 | export type Request = { 41 | method: string; 42 | url: string; 43 | body: Body | Body[]; 44 | options: RequestOptions; 45 | }; 46 | 47 | export type ExtractedFiles = { 48 | clone: unknown; 49 | files: Map; 50 | }; 51 | 52 | export type ExtractFiles = (body: Body | Body[]) => ExtractedFiles; 53 | -------------------------------------------------------------------------------- /scripts/prepare-e2e.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | const cwd = process.cwd(); 7 | const [, , name, version] = process.argv; 8 | 9 | function updateComponent() { 10 | let filepath = path.join(cwd, `./${name}/src/app/app.component.ts`); 11 | let suffix = 'Component'; 12 | 13 | if (!fs.existsSync(filepath)) { 14 | filepath = path.join(cwd, `./${name}/src/app/app.ts`); 15 | suffix = ''; 16 | } 17 | 18 | const code = 19 | `import { Apollo } from 'apollo-angular';\n` + 20 | `import { versionInfo } from 'graphql';\n` + 21 | fs 22 | .readFileSync(filepath, 'utf8') 23 | .replace(`App${suffix} {`, `App${suffix} { constructor(private readonly apollo: Apollo) {}`) + 24 | `\n (window as any).GRAPHQL_VERSION = versionInfo.major;`; 25 | 26 | fs.writeFileSync(filepath, code, 'utf8'); 27 | } 28 | 29 | function updateCypress() { 30 | let filepath = path.join(cwd, `./${name}/cypress/e2e/spec.cy.ts`); 31 | const code = fs 32 | .readFileSync(filepath, 'utf8') 33 | .replace( 34 | `cy.contains('app is running')`, 35 | `cy.window().its('GRAPHQL_VERSION').should('equal', ${version})`, 36 | ); 37 | 38 | fs.writeFileSync(filepath, code, 'utf8'); 39 | 40 | fs.writeFileSync( 41 | path.join(cwd, `./${name}/cypress/support/index.ts`), 42 | ` 43 | import failOnConsoleError from 'cypress-fail-on-console-error'; 44 | failOnConsoleError(); 45 | `, 46 | 'utf8', 47 | ); 48 | } 49 | 50 | updateComponent(); 51 | updateCypress(); 52 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: website 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | deployment: 13 | runs-on: ubuntu-latest 14 | if: 15 | github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 16 | 'push' 17 | steps: 18 | - name: checkout 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | 23 | - uses: the-guild-org/shared-config/setup@main 24 | name: website 25 | with: 26 | nodeVersion: 22 # change if needed 27 | packageManager: yarn # change if needed 28 | 29 | - name: Pre-install wrangler 30 | run: yarn add wrangler --ignore-workspace-root-check 31 | 32 | - uses: the-guild-org/shared-config/website-cf@main 33 | name: build and deploy website 34 | env: 35 | NEXT_BASE_PATH: 36 | ${{ github.ref == 'refs/heads/master' && '/graphql/apollo-angular' || '' }} 37 | SITE_URL: 38 | ${{ github.ref == 'refs/heads/master' && 'https://the-guild.dev/graphql/apollo-angular' 39 | || '' }} 40 | with: 41 | cloudflareApiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} 42 | cloudflareAccountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} 43 | githubToken: ${{ secrets.GITHUB_TOKEN }} 44 | projectName: apollo-angular 45 | prId: ${{ github.event.pull_request.number }} 46 | websiteDirectory: ./ 47 | buildScript: cd website && yarn && yarn build 48 | artifactDir: website/out 49 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/utils/file.cts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | import { SchematicsException, Tree } from '@angular-devkit/schematics'; 3 | 4 | export function getFileContent(host: Tree, path: string): string { 5 | const buffer = host.read(path); 6 | 7 | if (buffer === null) { 8 | throw new SchematicsException(`Couldn't read ${path}!`); 9 | } 10 | 11 | return buffer.toString('utf-8'); 12 | } 13 | 14 | export function parseJSON(path: string, content: string) { 15 | const { config, error } = ts.readConfigFile(path, () => content); 16 | 17 | if (error) { 18 | throw new SchematicsException(error.messageText.toString()); 19 | } 20 | 21 | return config; 22 | } 23 | 24 | /** 25 | * Returns the parsed content of a json file. 26 | * @param host {Tree} The source tree. 27 | * @param path {String} The path to the file to read. Relative to the root of the tree. 28 | */ 29 | export function getJsonFile(host: Tree, path: string) { 30 | return parseJSON(path, getFileContent(host, path)); 31 | } 32 | 33 | /** 34 | * Reads file from given path and Returns TypeScript source file. 35 | * @param host {Tree} The source tree. 36 | * @param path {String} The path to the file to read. Relative to the root of the tree. 37 | * */ 38 | export function getTypeScriptSourceFile(host: Tree, path: string): ts.SourceFile { 39 | const buffer = host.read(path); 40 | if (!buffer) { 41 | throw new SchematicsException(`Could not find ${path}!`); 42 | } 43 | 44 | const content = buffer.toString(); 45 | const sourceFile = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); 46 | 47 | return sourceFile; 48 | } 49 | -------------------------------------------------------------------------------- /packages/demo/src/app/pages/movies/movies-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Apollo, gql, onlyCompleteData } from 'apollo-angular'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | import { AsyncPipe } from '@angular/common'; 5 | import { Component, inject, OnInit } from '@angular/core'; 6 | import { RouterLink } from '@angular/router'; 7 | 8 | interface Film { 9 | id: number; 10 | title: string; 11 | director: string; 12 | releaseDate: string; 13 | } 14 | 15 | interface Query { 16 | allFilms: { films: Film[] }; 17 | } 18 | 19 | type Variables = Record; 20 | 21 | @Component({ 22 | selector: 'movies-page', 23 | template: ` 24 | @if (films$ | async; as films) { 25 |
    26 | @for (film of films; track film.id) { 27 |
  • 28 | {{ film.title }} 29 | by {{ film.director }} ({{ film.releaseDate }}) 30 |
  • 31 | } 32 |
33 | } @else { 34 |

Loading ...

35 | } 36 | `, 37 | standalone: true, 38 | imports: [RouterLink, AsyncPipe], 39 | }) 40 | export class MoviesPageComponent implements OnInit { 41 | films$!: Observable; 42 | private readonly apollo = inject(Apollo); 43 | 44 | ngOnInit() { 45 | this.films$ = this.apollo 46 | .watchQuery({ 47 | query: gql` 48 | query AllFilms { 49 | allFilms { 50 | films { 51 | id 52 | title 53 | director 54 | releaseDate 55 | } 56 | } 57 | } 58 | `, 59 | notifyOnNetworkStatusChange: false, 60 | }) 61 | .valueChanges.pipe( 62 | onlyCompleteData(), 63 | map(result => result.data.allFilms.films), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/query.ts: -------------------------------------------------------------------------------- 1 | import type { DocumentNode } from 'graphql'; 2 | import type { Observable } from 'rxjs'; 3 | import { Injectable } from '@angular/core'; 4 | import type { OperationVariables, TypedDocumentNode } from '@apollo/client'; 5 | import { Apollo } from './apollo'; 6 | import { QueryRef } from './query-ref'; 7 | import { EmptyObject } from './types'; 8 | 9 | export declare namespace Query { 10 | export type WatchOptions< 11 | TData = unknown, 12 | TVariables extends OperationVariables = EmptyObject, 13 | > = Omit, 'query'>; 14 | 15 | export type FetchOptions< 16 | TData = unknown, 17 | TVariables extends OperationVariables = EmptyObject, 18 | > = Omit, 'query'>; 19 | } 20 | 21 | @Injectable() 22 | export abstract class Query { 23 | public abstract readonly document: DocumentNode | TypedDocumentNode; 24 | public client = 'default'; 25 | 26 | constructor(protected readonly apollo: Apollo) {} 27 | 28 | public watch( 29 | ...[options]: {} extends TVariables 30 | ? [options?: Query.WatchOptions] 31 | : [options: Query.WatchOptions] 32 | ): QueryRef { 33 | return this.apollo.use(this.client).watchQuery({ 34 | ...options, 35 | query: this.document, 36 | } as Apollo.WatchQueryOptions); 37 | } 38 | 39 | public fetch( 40 | ...[options]: {} extends TVariables 41 | ? [options?: Query.FetchOptions] 42 | : [options: Query.FetchOptions] 43 | ): Observable> { 44 | return this.apollo.use(this.client).query({ 45 | ...options, 46 | query: this.document, 47 | } as Apollo.QueryOptions); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /website/src/components/index-page.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react'; 2 | import { FeatureList, HeroGradient } from '@theguild/components'; 3 | import UndrawBusiness from 'public/assets/undraw_business.svg'; 4 | import UndrawFormingIdeas from 'public/assets/undraw_forming_ideas.svg'; 5 | import UndrawNavigator from 'public/assets/undraw_navigator.svg'; 6 | 7 | export function IndexPage(): ReactElement { 8 | return ( 9 | <> 10 | 20 | 21 | 58 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/nativescript.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to use Apollo with NativeScript. You can use Apollo with NativeScript exactly as you 4 | would with normal Angular application. 5 | --- 6 | 7 | import { Callout } from '@theguild/components'; 8 | 9 | # Integrating with NativeScript 10 | 11 | You can use Apollo with NativeScript exactly as you would with normal Angular application. 12 | 13 | To introduce Apollo to your app, install `apollo-angular` from npm and use them in your app as 14 | outlined in the [setup](../get-started) article: 15 | 16 | ```sh npm2yarn 17 | npm i apollo-angular 18 | ``` 19 | 20 | 21 | **Note**: There are more packages to be installed, so check out the "initialization" article. 22 | 23 | 24 | ```ts filename="app.config.ts" 25 | import { provideApollo } from 'apollo-angular'; 26 | import { HttpLink } from 'apollo-angular/http'; 27 | import { NativeScriptHttpClientModule } from 'nativescript-angular/http-client'; 28 | import { NativeScriptModule } from 'nativescript-angular/nativescript.module'; 29 | import { ApplicationConfig, importProvidersFrom, inject } from '@angular/core'; 30 | import { InMemoryCache } from '@apollo/client'; 31 | 32 | export const appConfig: ApplicationConfig = { 33 | providers: [ 34 | importProvidersFrom(NativeScriptModule, NativeScriptHttpClientModule), // this provides HttpClient 35 | provideApollo(() => { 36 | const httpLink = inject(HttpLink); 37 | 38 | return { 39 | link: httpLink.create({ uri: '/graphql' }), 40 | cache: new InMemoryCache(), 41 | // other options ... 42 | }; 43 | }), 44 | ], 45 | }; 46 | ``` 47 | 48 | If you are new to using Apollo with Angular, you should probably read the [Angular guide](/). 49 | 50 | ## Examples 51 | 52 | There are some Apollo examples written in NativeScript that you may wish to refer to: 53 | 54 | - The ["Posts and Authors" example](https://github.com/kamilkisiela/apollo-angular-nativescript). 55 | 56 | 57 | If you've got an example to post here, please hit the "Edit this page on GitHub" button above and 58 | let us know! 59 | 60 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/only-complete-data.ts: -------------------------------------------------------------------------------- 1 | import { filter, type OperatorFunction } from 'rxjs'; 2 | import type { ApolloClient, GetDataState, ObservableQuery } from '@apollo/client/core'; 3 | 4 | type CompleteFragment = { 5 | complete: true; 6 | missing?: never; 7 | } & GetDataState; 8 | 9 | type ForWatchFragment = OperatorFunction< 10 | ApolloClient.WatchFragmentResult, 11 | CompleteFragment 12 | >; 13 | 14 | /** 15 | * Filter emitted results to only receive results that are complete (`result.dataState === 'complete'`). 16 | * 17 | * This is a small wrapper around rxjs `filter()` for convenience only. 18 | * 19 | * If you use this, you should probably combine it with [`notifyOnNetworkStatusChange`](https://www.apollographql.com/docs/react/data/queries#queryhookoptions-interface-notifyonnetworkstatuschange). 20 | * This tells `@apollo/client` to not emit the first `partial` result, so `apollo-angular` does 21 | * not need to filter it out. The overall behavior is identical, but it saves some CPU cycles. 22 | * 23 | * So something like this: 24 | * 25 | * ```ts 26 | * apollo 27 | * .watchQuery({ 28 | * query: myQuery, 29 | * notifyOnNetworkStatusChange: false, // Adding this will save CPU cycles 30 | * }) 31 | * .valueChanges 32 | * .pipe(onlyCompleteData()) 33 | * .subscribe(result => { 34 | * // Do something with complete result 35 | * }); 36 | * ``` 37 | */ 38 | export function onlyCompleteData(): OperatorFunction< 39 | ObservableQuery.Result, 40 | ObservableQuery.Result 41 | > { 42 | return filter( 43 | (result): result is ObservableQuery.Result => 44 | result.dataState === 'complete', 45 | ); 46 | } 47 | 48 | /** 49 | * @deprecated Use `onlyCompleteData()` instead. 50 | */ 51 | export const onlyComplete = onlyCompleteData; 52 | 53 | /** 54 | * Same as `onlyCompleteData()` but for `Apollo.watchFragment()`. 55 | */ 56 | export function onlyCompleteFragment(): ForWatchFragment { 57 | return filter((result): result is CompleteFragment => result.dataState === 'complete'); 58 | } 59 | -------------------------------------------------------------------------------- /packages/demo/src/app/pages/movie/movie-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Apollo, gql, onlyCompleteData } from 'apollo-angular'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | import { AsyncPipe } from '@angular/common'; 5 | import { Component, inject, OnInit } from '@angular/core'; 6 | import { ActivatedRoute, RouterLink } from '@angular/router'; 7 | 8 | interface Character { 9 | id: string; 10 | name: string; 11 | } 12 | 13 | interface Film { 14 | title: string; 15 | characterConnection: { 16 | characters: Character[]; 17 | }; 18 | } 19 | 20 | interface Query { 21 | film: Film; 22 | } 23 | 24 | interface Variables { 25 | id: string; 26 | } 27 | 28 | @Component({ 29 | selector: 'author-page', 30 | template: ` 31 | @if (film$ | async; as film) { 32 |

Characters seen in {{ film.title }}

33 |
    34 | @for (character of film.characterConnection.characters; track character.id) { 35 |
  • 36 | {{ character.name }} 37 |
  • 38 | } 39 |
40 | } @else { 41 |

Loading ...

42 | } 43 | Back to movies 44 | `, 45 | standalone: true, 46 | imports: [RouterLink, AsyncPipe], 47 | }) 48 | export class MoviePageComponent implements OnInit { 49 | film$!: Observable; 50 | private readonly apollo = inject(Apollo); 51 | private readonly route = inject(ActivatedRoute); 52 | 53 | ngOnInit() { 54 | this.film$ = this.apollo 55 | .watchQuery({ 56 | query: gql` 57 | query FilmCharacters($id: ID) { 58 | film(id: $id) { 59 | title 60 | characterConnection { 61 | characters { 62 | id 63 | name 64 | } 65 | } 66 | } 67 | } 68 | `, 69 | variables: { 70 | id: this.route.snapshot.paramMap.get('id')!, 71 | }, 72 | notifyOnNetworkStatusChange: false, 73 | }) 74 | .valueChanges.pipe( 75 | onlyCompleteData(), 76 | map(result => result.data.film), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/apollo-angular/headers/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { of } from 'rxjs'; 2 | import { describe, expect, test } from 'vitest'; 3 | import { HttpHeaders } from '@angular/common/http'; 4 | import { ApolloClient, ApolloLink, execute, gql, InMemoryCache } from '@apollo/client'; 5 | import { HttpHeadersLink } from '../src'; 6 | 7 | const query = gql` 8 | query heroes { 9 | heroes { 10 | name 11 | __typename 12 | } 13 | } 14 | `; 15 | const data = { heroes: [{ name: 'Foo', __typename: 'Hero' }] }; 16 | 17 | const dummyClient = new ApolloClient({ cache: new InMemoryCache(), link: ApolloLink.empty() }); 18 | 19 | describe('HttpHeadersLink', () => { 20 | test('should turn object into HttpHeaders', () => 21 | new Promise(done => { 22 | const headersLink = new HttpHeadersLink(); 23 | 24 | const mockLink = new ApolloLink(operation => { 25 | const { headers } = operation.getContext(); 26 | 27 | expect(headers instanceof HttpHeaders).toBe(true); 28 | expect(headers.get('Authorization')).toBe('Bearer Foo'); 29 | 30 | return of({ data }); 31 | }); 32 | 33 | const link = headersLink.concat(mockLink); 34 | 35 | execute( 36 | link, 37 | { 38 | query, 39 | context: { 40 | headers: { 41 | Authorization: 'Bearer Foo', 42 | }, 43 | }, 44 | }, 45 | { client: dummyClient }, 46 | ).subscribe(result => { 47 | expect(result.data).toEqual(data); 48 | done(); 49 | }); 50 | })); 51 | 52 | test('should not set headers when not defined', () => 53 | new Promise(done => { 54 | const headersLink = new HttpHeadersLink(); 55 | 56 | const mockLink = new ApolloLink(operation => { 57 | const { headers } = operation.getContext(); 58 | 59 | expect(headers).toBeUndefined(); 60 | 61 | return of({ data }); 62 | }); 63 | 64 | const link = headersLink.concat(mockLink); 65 | 66 | execute(link, { query }, { client: dummyClient }).subscribe(result => { 67 | expect(result.data).toEqual(data); 68 | done(); 69 | }); 70 | })); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/apollo-angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-angular", 3 | "version": "13.0.0", 4 | "type": "module", 5 | "description": "Use your GraphQL data in your Angular app, with the Apollo Client", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/the-guild-org/apollo-angular", 9 | "directory": "packages/apollo-angular" 10 | }, 11 | "homepage": "https://www.apollo-angular.com/", 12 | "contributors": [ 13 | { 14 | "name": "Kamil Kisiela", 15 | "email": "kamil.kisiela@gmail.com", 16 | "url": "https://github.com/kamilkisiela/" 17 | } 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">=16" 22 | }, 23 | "module": "build/fesm2020/ngApollo.mjs", 24 | "typings": "build/index.d.ts", 25 | "keywords": [ 26 | "apollo", 27 | "graphql", 28 | "angular", 29 | "schematics", 30 | "angular-schematics" 31 | ], 32 | "scripts": { 33 | "build": "ng-packagr -p ng-package.json && yarn build:schematics", 34 | "build:schematics": "tsc -p schematics/tsconfig.json && node ./scripts/move-schematics.js", 35 | "prebuild": "rm -rf build/", 36 | "release": "yarn build && changeset publish", 37 | "test": "vitest --run && yarn test:schematics", 38 | "test:schematics": "tsc -p schematics/tsconfig.test.json && node scripts/move-schematics.js && jasmine --config=schematics/jasmine.json" 39 | }, 40 | "peerDependencies": { 41 | "@angular/core": "^19.0.0 || ^20.0.0 || ^21.0.0", 42 | "@apollo/client": "^4.0.1", 43 | "graphql": "^16.0.0", 44 | "rxjs": "^7.8.0" 45 | }, 46 | "dependencies": { 47 | "tslib": "^2.6.2" 48 | }, 49 | "devDependencies": { 50 | "@analogjs/vite-plugin-angular": "^2.1.1", 51 | "@analogjs/vitest-angular": "^2.1.1", 52 | "@angular/build": "^19.2.0", 53 | "@types/jasmine": "^5.1.7", 54 | "@types/node": "^20.12.12", 55 | "jasmine": "^5.6.0", 56 | "jsdom": "^26.0.0", 57 | "vite-tsconfig-paths": "^5.1.4", 58 | "vitest": "^4.0.14" 59 | }, 60 | "publishConfig": { 61 | "directory": "build", 62 | "access": "public", 63 | "provenance": true 64 | }, 65 | "sideEffects": false, 66 | "schematics": "./schematics/collection.json" 67 | } 68 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Observable, queueScheduler, SchedulerAction, SchedulerLike, Subscription } from 'rxjs'; 2 | import { map, observeOn, startWith } from 'rxjs/operators'; 3 | import { NgZone } from '@angular/core'; 4 | import type { ApolloClient } from '@apollo/client'; 5 | import { Apollo } from './apollo'; 6 | 7 | /** 8 | * Like RxJS's `fromPromise()`, but starts the promise only when the observable is subscribed to. 9 | */ 10 | export function fromLazyPromise(promiseFn: () => Promise): Observable { 11 | return new Observable(subscriber => { 12 | promiseFn().then( 13 | result => { 14 | if (!subscriber.closed) { 15 | subscriber.next(result); 16 | subscriber.complete(); 17 | } 18 | }, 19 | error => { 20 | if (!subscriber.closed) { 21 | subscriber.error(error); 22 | } 23 | }, 24 | ); 25 | 26 | return () => subscriber.unsubscribe(); 27 | }); 28 | } 29 | 30 | export function useMutationLoading( 31 | source: Observable>, 32 | enabled: boolean, 33 | ) { 34 | if (!enabled) { 35 | return source.pipe( 36 | map, Apollo.MutateResult>(result => ({ 37 | ...result, 38 | loading: false, 39 | })), 40 | ); 41 | } 42 | 43 | return source.pipe( 44 | map, Apollo.MutateResult>(result => ({ 45 | ...result, 46 | loading: false, 47 | })), 48 | startWith>({ 49 | data: undefined, 50 | loading: true, 51 | }), 52 | ); 53 | } 54 | 55 | export class ZoneScheduler implements SchedulerLike { 56 | constructor(private readonly zone: NgZone) {} 57 | 58 | public readonly now = Date.now; 59 | 60 | public schedule( 61 | work: (this: SchedulerAction, state?: T) => void, 62 | delay: number = 0, 63 | state?: T, 64 | ): Subscription { 65 | return this.zone.run(() => queueScheduler.schedule(work, delay, state)) as Subscription; 66 | } 67 | } 68 | 69 | export function wrapWithZone(obs: Observable, ngZone: NgZone): Observable { 70 | return obs.pipe(observeOn(new ZoneScheduler(ngZone))); 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-angular-monorepo", 3 | "license": "MIT", 4 | "private": true, 5 | "workspaces": { 6 | "packages": [ 7 | "packages/*", 8 | "website" 9 | ] 10 | }, 11 | "engines": { 12 | "node": ">=16" 13 | }, 14 | "scripts": { 15 | "build": "yarn workspaces run build", 16 | "ci-only:integration": "./scripts/integration tests", 17 | "deploy": "yarn workspaces run deploy", 18 | "format": "prettier --write \"{packages,website}/**/*.{ts,tsx,json}\"", 19 | "now-build": "(cd website && yarn && yarn build && mv build ../public)", 20 | "prebuild": "rimraf packages/*/build/", 21 | "prerelease": "yarn build", 22 | "release": "changeset publish", 23 | "test": "yarn workspaces run test", 24 | "test:e2e": "./scripts/run-e2e-locally.sh" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "^19.2.0", 28 | "@angular-devkit/core": "^19.2.0", 29 | "@angular-devkit/schematics": "^19.2.0", 30 | "@angular/animations": "^19.2.0", 31 | "@angular/cli": "^19.2.0", 32 | "@angular/common": "^19.2.0", 33 | "@angular/compiler": "^19.2.0", 34 | "@angular/compiler-cli": "^19.2.0", 35 | "@angular/core": "^19.2.0", 36 | "@angular/platform-browser": "^19.2.0", 37 | "@angular/platform-browser-dynamic": "^19.2.0", 38 | "@angular/platform-server": "^19.2.0", 39 | "@angular/router": "^19.2.0", 40 | "@apollo/client": "4.0.1", 41 | "@babel/core": "^7.24.6", 42 | "@babel/preset-env": "^7.24.6", 43 | "@changesets/changelog-github": "^0.5.0", 44 | "@changesets/cli": "^2.27.3", 45 | "@schematics/angular": "^19.2.0", 46 | "@theguild/prettier-config": "^2.0.1", 47 | "@types/node": "^20.12.12", 48 | "browserlist": "^1.0.1", 49 | "graphql": "^16.8.0", 50 | "husky": "^9.0.0", 51 | "lint-staged": "^15.2.5", 52 | "ng-packagr": "^19.2.0", 53 | "prettier": "^3.7.1", 54 | "react": "^18.3.1", 55 | "rimraf": "^5.0.7", 56 | "rxjs": "~7.8.0", 57 | "shelljs": "^0.8.5", 58 | "tslib": "^2.3.0", 59 | "typescript": "~5.7.2", 60 | "zone.js": "~0.15.0" 61 | }, 62 | "husky": { 63 | "hooks": { 64 | "pre-commit": "lint-staged" 65 | } 66 | }, 67 | "lint-staged": { 68 | "packages/**/{src,tests}/**/*.ts": [ 69 | "prettier --write" 70 | ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/tests/module.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { gql, InMemoryCache } from '@apollo/client'; 4 | import { Apollo } from '../../src'; 5 | import { APOLLO_TESTING_CACHE, ApolloTestingController, ApolloTestingModule } from '../src'; 6 | 7 | describe('ApolloTestingModule', () => { 8 | test('should provide a default ApolloCache', () => { 9 | TestBed.configureTestingModule({ 10 | imports: [ApolloTestingModule], 11 | }); 12 | 13 | const apollo = TestBed.inject(Apollo); 14 | const cache = apollo.client.cache as InMemoryCache; 15 | 16 | expect(cache).toBeInstanceOf(InMemoryCache); 17 | }); 18 | 19 | test('should allow to use custom ApolloCache', () => { 20 | const cache = new InMemoryCache(); 21 | 22 | TestBed.configureTestingModule({ 23 | imports: [ApolloTestingModule], 24 | providers: [ 25 | { 26 | provide: APOLLO_TESTING_CACHE, 27 | useValue: cache, 28 | }, 29 | ], 30 | }); 31 | 32 | const apollo = TestBed.inject(Apollo); 33 | 34 | expect(apollo.client.cache).toBe(cache); 35 | }); 36 | 37 | test('should not modify test data', () => 38 | new Promise(done => { 39 | TestBed.configureTestingModule({ 40 | imports: [ApolloTestingModule], 41 | }); 42 | 43 | const apollo = TestBed.inject(Apollo); 44 | const backend = TestBed.inject(ApolloTestingController); 45 | 46 | const testQuery = gql` 47 | query allHeroes { 48 | heroes { 49 | name 50 | } 51 | } 52 | `; 53 | 54 | const testData = [ 55 | { 56 | id: '1', 57 | name: 'Spiderman', 58 | }, 59 | { 60 | id: '2', 61 | name: 'Batman', 62 | }, 63 | ]; 64 | const testGqlData = { 65 | data: { 66 | heroes: testData, 67 | }, 68 | }; 69 | 70 | apollo 71 | .query({ 72 | query: testQuery, 73 | }) 74 | .subscribe(result => { 75 | expect(result.data.heroes[0].name).toBe('Spiderman'); 76 | done(); 77 | }); 78 | 79 | backend.expectOne('allHeroes').flush(testGqlData); 80 | expect(testGqlData.data.heroes).toEqual(testData); 81 | })); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/demo/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "demo": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | }, 12 | "@schematics/angular:application": { 13 | "strict": true 14 | } 15 | }, 16 | "root": "", 17 | "sourceRoot": "src", 18 | "prefix": "app", 19 | "architect": { 20 | "build": { 21 | "builder": "@angular-devkit/build-angular:browser-esbuild", 22 | "options": { 23 | "outputPath": "dist/demo", 24 | "index": "src/index.html", 25 | "main": "src/main.ts", 26 | "polyfills": "src/polyfills.ts", 27 | "tsConfig": "tsconfig.app.json", 28 | "inlineStyleLanguage": "scss", 29 | "assets": ["src/favicon.ico", "src/assets"], 30 | "styles": ["src/styles.scss"], 31 | "scripts": [] 32 | }, 33 | "configurations": { 34 | "production": { 35 | "budgets": [ 36 | { 37 | "type": "initial", 38 | "maximumWarning": "500kb", 39 | "maximumError": "1mb" 40 | }, 41 | { 42 | "type": "anyComponentStyle", 43 | "maximumWarning": "2kb", 44 | "maximumError": "4kb" 45 | } 46 | ], 47 | "outputHashing": "all" 48 | }, 49 | "development": { 50 | "optimization": false, 51 | "extractLicenses": false, 52 | "sourceMap": true 53 | } 54 | }, 55 | "defaultConfiguration": "production" 56 | }, 57 | "serve": { 58 | "builder": "@angular-devkit/build-angular:dev-server", 59 | "configurations": { 60 | "production": { 61 | "buildTarget": "demo:build:production" 62 | }, 63 | "development": { 64 | "buildTarget": "demo:build:development" 65 | } 66 | }, 67 | "defaultConfiguration": "development" 68 | } 69 | } 70 | } 71 | }, 72 | "cli": { 73 | "analytics": false 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.devcontainer/base.Dockerfile: -------------------------------------------------------------------------------- 1 | # Update the VARIANT arg in devcontainer.json to pick a Node.js version: 14, 12, 10 2 | ARG VARIANT=14 3 | FROM node:${VARIANT} 4 | 5 | # Options for setup scripts 6 | ARG INSTALL_ZSH="true" 7 | ARG UPGRADE_PACKAGES="true" 8 | ARG USERNAME=node 9 | ARG USER_UID=1000 10 | ARG USER_GID=$USER_UID 11 | 12 | ENV NVM_DIR=/usr/local/share/nvm 13 | ENV NVM_SYMLINK_CURRENT=true \ 14 | PATH=${NVM_DIR}/current/bin:${PATH} 15 | 16 | # Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies. 17 | COPY library-scripts/*.sh /tmp/library-scripts/ 18 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 19 | # Remove imagemagick due to https://security-tracker.debian.org/tracker/CVE-2019-10131 20 | && apt-get purge -y imagemagick imagemagick-6-common \ 21 | # Install common packages, non-root user, update yarn and install nvm 22 | && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \ 23 | && rm -rf /opt/yarn-* /usr/local/bin/yarn /usr/local/bin/yarnpkg \ 24 | && /bin/bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "none" "${USERNAME}" \ 25 | && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /root/.gnupg /tmp/library-scripts 26 | 27 | # Configure global npm install location 28 | ARG NPM_GLOBAL=/usr/local/share/npm-global 29 | ENV PATH=${PATH}:${NPM_GLOBAL}/bin 30 | RUN mkdir -p ${NPM_GLOBAL} \ 31 | && chown ${USERNAME}:root ${NPM_GLOBAL} \ 32 | && npm config -g set prefix ${NPM_GLOBAL} \ 33 | && sudo -u ${USERNAME} npm config -g set prefix ${NPM_GLOBAL} \ 34 | && echo "if [ \"\$(stat -c '%U' ${NPM_GLOBAL})\" != \"${USERNAME}\" ]; then sudo chown -R ${USER_UID}:root ${NPM_GLOBAL} ${NVM_DIR}; fi" \ 35 | | tee -a /root/.bashrc /root/.zshrc /home/${USERNAME}/.bashrc >> /home/${USERNAME}/.zshrc 36 | 37 | # Install eslint globally 38 | RUN sudo -u ${USERNAME} npm install -g eslint 39 | 40 | # [Optional] Uncomment this section to install additional OS packages. 41 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 42 | # && apt-get -y install --no-install-recommends 43 | 44 | # [Optional] Uncomment if you want to install an additional version of node using nvm 45 | # ARG EXTRA_NODE_VERSION=10 46 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 47 | -------------------------------------------------------------------------------- /packages/apollo-angular/test-utils/matchers/toEmitAnything.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapted from 3 | * https://github.com/apollographql/apollo-client/blob/1d165ba37eca7e5d667055553aacc4c26be56065/src/testing/matchers/toEmitAnything.ts 4 | * 5 | * The MIT License (MIT) 6 | * 7 | * Copyright (c) 2022 Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.) 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in all 17 | * copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | * SOFTWARE. 26 | */ 27 | import { ObservableStream, TakeOptions } from 'test-utils/ObservableStream'; 28 | import { RawMatcherFn } from '@vitest/expect'; 29 | 30 | export const toEmitAnything: RawMatcherFn = async function toEmitAnything( 31 | actual, 32 | options?: TakeOptions, 33 | ) { 34 | const stream = actual as ObservableStream; 35 | const hint = this.utils.matcherHint('toEmitAnything', 'stream'); 36 | 37 | try { 38 | const value = await stream.peek(options); 39 | 40 | return { 41 | pass: true, 42 | message: () => { 43 | return ( 44 | hint + 45 | '\n\nExpected stream not to emit anything but it did.' + 46 | '\n\nReceived:\n' + 47 | this.utils.printReceived(value) 48 | ); 49 | }, 50 | }; 51 | } catch (error) { 52 | if (error instanceof Error && error.message === 'Timeout waiting for next event') { 53 | return { 54 | pass: false, 55 | message: () => hint + '\n\nExpected stream to emit an event but it did not.', 56 | }; 57 | } else { 58 | throw error; 59 | } 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /packages/apollo-angular/tests/Mutation.spec.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, Mock, test, vi } from 'vitest'; 2 | import { Injectable } from '@angular/core'; 3 | import { TestBed } from '@angular/core/testing'; 4 | import { Apollo, gql, Mutation } from '../src'; 5 | 6 | const mutation = gql` 7 | mutation addHero($name: String) { 8 | addHero(name: $name) { 9 | name 10 | } 11 | } 12 | `; 13 | 14 | @Injectable() 15 | export class AddHeroMutation extends Mutation { 16 | public document = mutation; 17 | } 18 | 19 | describe('Mutation', () => { 20 | let apolloMock: Apollo & { mutate: Mock }; 21 | let addHero: AddHeroMutation; 22 | 23 | function createApollo() { 24 | apolloMock = { 25 | mutate: vi.fn(), 26 | use(name: string) { 27 | if (name === 'default') { 28 | return apolloMock; 29 | } 30 | }, 31 | } as any; 32 | 33 | return apolloMock; 34 | } 35 | 36 | beforeEach(() => { 37 | TestBed.configureTestingModule({ 38 | providers: [ 39 | { 40 | provide: Apollo, 41 | useFactory: createApollo, 42 | }, 43 | AddHeroMutation, 44 | ], 45 | }); 46 | 47 | addHero = TestBed.inject(AddHeroMutation); 48 | }); 49 | 50 | test('should have document defined', () => { 51 | expect(addHero.document).toEqual(mutation); 52 | }); 53 | 54 | test('should use watchQuery under the hood', () => { 55 | apolloMock.mutate.mockReturnValue('FetchResult'); 56 | 57 | const result = addHero.mutate(); 58 | 59 | expect(apolloMock.mutate).toBeCalled(); 60 | expect(result).toEqual('FetchResult'); 61 | }); 62 | 63 | test('should pass variables to Apollo.mutate', () => { 64 | addHero.mutate({ variables: { foo: 1 } }); 65 | 66 | expect(apolloMock.mutate).toBeCalled(); 67 | expect(apolloMock.mutate.mock.calls[0][0]).toMatchObject({ 68 | variables: { foo: 1 }, 69 | }); 70 | }); 71 | 72 | test('should pass options to Apollo.mutate', () => { 73 | addHero.mutate({ fetchPolicy: 'no-cache' }); 74 | 75 | expect(apolloMock.mutate).toBeCalled(); 76 | expect(apolloMock.mutate.mock.calls[0][0]).toMatchObject({ 77 | fetchPolicy: 'no-cache', 78 | }); 79 | }); 80 | 81 | test('should not overwrite query when options object is provided', () => { 82 | addHero.mutate({ mutation: 'asd', fetchPolicy: 'cache-first' } as any); 83 | 84 | expect(apolloMock.mutate).toBeCalled(); 85 | expect(apolloMock.mutate.mock.calls[0][0]).toMatchObject({ 86 | mutation, 87 | fetchPolicy: 'cache-first', 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /packages/apollo-angular/tests/Subscription.spec.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, Mock, test, vi } from 'vitest'; 2 | import { Injectable } from '@angular/core'; 3 | import { TestBed } from '@angular/core/testing'; 4 | import { Apollo, gql, Subscription } from '../src'; 5 | 6 | const query = gql` 7 | query heroes { 8 | allHeroes { 9 | name 10 | } 11 | } 12 | `; 13 | 14 | @Injectable() 15 | export class HeroesSubscription extends Subscription { 16 | public document = query; 17 | } 18 | 19 | describe('Subscription', () => { 20 | let apolloMock: Apollo & { subscribe: Mock }; 21 | let heroes: HeroesSubscription; 22 | 23 | function createApollo() { 24 | apolloMock = { 25 | subscribe: vi.fn(), 26 | use(name: string) { 27 | if (name === 'default') { 28 | return apolloMock; 29 | } 30 | }, 31 | } as any; 32 | 33 | return apolloMock; 34 | } 35 | 36 | beforeEach(() => { 37 | TestBed.configureTestingModule({ 38 | providers: [ 39 | { 40 | provide: Apollo, 41 | useFactory: createApollo, 42 | }, 43 | HeroesSubscription, 44 | ], 45 | }); 46 | heroes = TestBed.inject(HeroesSubscription); 47 | }); 48 | 49 | test('should have document defined', () => { 50 | expect(heroes.document).toEqual(query); 51 | }); 52 | 53 | test('should use subscribe under the hood', () => { 54 | apolloMock.subscribe.mockReturnValue('FetchResult'); 55 | 56 | const result = heroes.subscribe(); 57 | 58 | expect(apolloMock.subscribe).toBeCalled(); 59 | expect(result).toEqual('FetchResult'); 60 | }); 61 | 62 | test('should pass variables to Apollo.subscribe', () => { 63 | heroes.subscribe({ variables: { foo: 1 } }); 64 | 65 | expect(apolloMock.subscribe).toBeCalled(); 66 | expect(apolloMock.subscribe.mock.calls[0][0]).toMatchObject({ 67 | variables: { foo: 1 }, 68 | }); 69 | }); 70 | 71 | test('should pass options to Apollo.subscribe', () => { 72 | heroes.subscribe({ fetchPolicy: 'network-only' }); 73 | 74 | expect(apolloMock.subscribe).toBeCalled(); 75 | expect(apolloMock.subscribe.mock.calls[0][0]).toMatchObject({ 76 | fetchPolicy: 'network-only', 77 | }); 78 | }); 79 | 80 | test('should not overwrite query when options object is provided', () => { 81 | heroes.subscribe({ query: 'asd', fetchPolicy: 'cache-first' } as any); 82 | 83 | expect(apolloMock.subscribe).toBeCalled(); 84 | expect(apolloMock.subscribe.mock.calls[0][0]).toMatchObject({ 85 | query, 86 | fetchPolicy: 'cache-first', 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/src/module.ts: -------------------------------------------------------------------------------- 1 | import { Apollo } from 'apollo-angular'; 2 | import { Inject, InjectionToken, NgModule, Optional } from '@angular/core'; 3 | import { ApolloCache, ApolloLink, InMemoryCache } from '@apollo/client'; 4 | import { ApolloTestingBackend } from './backend'; 5 | import { ApolloTestingController } from './controller'; 6 | import { Operation } from './operation'; 7 | 8 | export type NamedCaches = Record; 9 | 10 | export const APOLLO_TESTING_CACHE = new InjectionToken('apollo-angular/testing cache'); 11 | 12 | export const APOLLO_TESTING_NAMED_CACHE = new InjectionToken( 13 | 'apollo-angular/testing named cache', 14 | ); 15 | 16 | export const APOLLO_TESTING_CLIENTS = new InjectionToken( 17 | 'apollo-angular/testing named clients', 18 | ); 19 | 20 | function addClient(name: string, op: ApolloLink.Operation): Operation { 21 | (op as Operation).clientName = name; 22 | 23 | return op as Operation; 24 | } 25 | 26 | @NgModule({ 27 | providers: [ 28 | Apollo, 29 | ApolloTestingBackend, 30 | { provide: ApolloTestingController, useExisting: ApolloTestingBackend }, 31 | ], 32 | }) 33 | export class ApolloTestingModuleCore { 34 | constructor( 35 | apollo: Apollo, 36 | backend: ApolloTestingBackend, 37 | @Optional() 38 | @Inject(APOLLO_TESTING_CLIENTS) 39 | namedClients?: string[], 40 | @Optional() 41 | @Inject(APOLLO_TESTING_CACHE) 42 | cache?: ApolloCache, 43 | @Optional() 44 | @Inject(APOLLO_TESTING_NAMED_CACHE) 45 | namedCaches?: NamedCaches, 46 | ) { 47 | function createOptions(name: string, c?: ApolloCache | null) { 48 | return { 49 | connectToDevTools: false, 50 | link: new ApolloLink(operation => backend.handle(addClient(name, operation))), 51 | cache: c || new InMemoryCache(), 52 | }; 53 | } 54 | 55 | apollo.create(createOptions('default', cache)); 56 | 57 | if (namedClients && namedClients.length) { 58 | namedClients.forEach(name => { 59 | const caches = namedCaches && typeof namedCaches === 'object' ? namedCaches : {}; 60 | 61 | apollo.createNamed(name, createOptions(name, caches[name])); 62 | }); 63 | } 64 | } 65 | } 66 | 67 | @NgModule({ 68 | imports: [ApolloTestingModuleCore], 69 | }) 70 | export class ApolloTestingModule { 71 | static withClients(names: string[]) { 72 | return { 73 | ngModule: ApolloTestingModuleCore, 74 | providers: [ 75 | { 76 | provide: APOLLO_TESTING_CLIENTS, 77 | useValue: names, 78 | }, 79 | ], 80 | }; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /website/src/pages/docs/local-state/management.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to manage local state alongside remotely fetched state with Apollo Client, using field 4 | policies and reactive variables. 5 | --- 6 | 7 | # Managing Local State 8 | 9 | At its core, Apollo Client is a **state management** library that happens to use GraphQL to interact 10 | with a remote server. Naturally, some application state doesn't require a remote server because it's 11 | entirely local. 12 | 13 | Apollo Client enables you to manage local state alongside remotely fetched state, meaning you can 14 | interact with all of your application's state with a single API. 15 | 16 | ## How It Works 17 | 18 | Please read the 19 | ["How it works"](https://www.apollographql.com/docs/react/local-state/local-state-management/#how-it-works) 20 | chapter on Apollo Client documentation. 21 | 22 | ## Field Policies and Local-Only Fields 23 | 24 | **Field policies** enable you to define what happens when you query a particular field, including 25 | fields that aren't defined in your GraphQL server's schema. By defining field policies for these 26 | **local-only fields**, you can populate them with data that's stored anywhere, such as in 27 | `localStorage` or [reactive variables](#reactive-variables). 28 | 29 | A single GraphQL query can include both local-only fields and remotely fetched fields. In the field 30 | policy for each local-only field, you specify a function that defines how that field's value is 31 | populated. 32 | 33 | [Get started with local-only fields](./managing-state-with-field-policies) 34 | 35 | ## Reactive Variables 36 | 37 | **Reactive variables** enable you to read and write local data anywhere in your application, without 38 | needing to use a GraphQL operation to do so. The field policy of a local-only field can use a 39 | reactive variable to populate the field's current value. 40 | 41 | Reactive variables aren't stored in the Apollo Client cache, so they don't need to conform to the 42 | strict structure of a cached type. You can store anything you want in them. 43 | 44 | Whenever the value of a reactive variable changes, Apollo Client automatically detects that change. 45 | Every active query with a field that depends on the changed variable automatically updates. 46 | 47 | [Get started with reactive variables](./reactive-variables) 48 | 49 | ## Local Resolvers 50 | 51 | In Apollo Client, you can define local resolvers to populate and modify local-only fields. These 52 | resolvers are similar in structure and purpose to the resolvers that your GraphQL server defines. 53 | 54 | [Learn more about local resolvers](https://www.apollographql.com/docs/react/local-state/local-resolvers/) 55 | -------------------------------------------------------------------------------- /website/src/pages/docs/performance/improving-performance.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to improve your application's performance with Apollo Client! Redirect queries to cached 4 | data and use prefetching to load data before it's needed. Read more in the Apollo Client 5 | documentation. 6 | --- 7 | 8 | # Improving Performance 9 | 10 | ## Redirecting to Cached Data 11 | 12 | In some cases, a query requests data that already exists in the client cache under a different 13 | reference. A very common example of this is when your UI has a list view and a detail view that both 14 | use the same data. To avoid re-requesting data that already exists in the cache, see 15 | [Cache redirects using field policy read functions](../caching/advanced-topics#cache-redirects-using-field-policy-read-functions). 16 | 17 | ## Prefetching Data 18 | 19 | One of the easiest ways to make your application's UI feel a lot snappier with Apollo Client is to 20 | use prefetching. Prefetching simply means fetching data before it needs to be rendered on the 21 | screen, for example by loading all data required for a view as soon as you anticipate that a user 22 | will navigate to it. 23 | 24 | In Apollo Client, prefetching is very simple and can be done by running a component's query before 25 | rendering. 26 | 27 | GitHunt uses `Apollo` and calls `query` method as soon as the user hovers over a link to the 28 | comments page. With the data prefetched, the comments page renders immediately, and the user often 29 | experiences no delay at all: 30 | 31 | ```ts 32 | import { Apollo, gql } from 'apollo-angular'; 33 | 34 | @Component({ 35 | template: ` 36 | 37 | View comments ({{ commentCount }}) 38 | 39 | `, 40 | }) 41 | class RepoInfoComponent { 42 | org: string; 43 | fullName: string; 44 | repoName: string; 45 | entry: any; 46 | 47 | constructor(private readonly apollo: Apollo) {} 48 | 49 | prefetchComments(repoFullName: string) { 50 | this.apollo 51 | .query({ 52 | query: commentQuery, 53 | variables: { repoName: repoFullName }, 54 | }) 55 | .subscribe(); 56 | } 57 | } 58 | ``` 59 | 60 | There are a lot of different ways to anticipate that the user will end up needing some data in the 61 | UI. In addition to using the hover state, here are some other places you can preload data: 62 | 63 | 1. The next step of a multistep wizard immediately 64 | 1. The route of a call-to-action button 65 | 1. All the data for a sub-area of the application, to make navigating within that area instant 66 | 67 | If you have some other ideas, please send a PR to this article, and maybe add some more code 68 | snippets. A special form of prefetching is 69 | [store hydration from the server](./server-side-rendering#store-rehydration), so you might also 70 | consider hydrating more data than is actually needed for the first page load to make other 71 | interactions faster. 72 | -------------------------------------------------------------------------------- /website/next.config.js: -------------------------------------------------------------------------------- 1 | import { withGuildDocs } from '@theguild/components/next.config'; 2 | 3 | export default withGuildDocs({ 4 | output: 'export', 5 | redirects: () => 6 | Object.entries({ 7 | '/docs/features/subscriptions.html': '/docs/data/subscriptions', 8 | 9 | '/docs/basics/:slug*': '/docs/data/:slug', 10 | 11 | '/docs/features/developer-tooling': '/docs/development-and-testing/developer-tools', 12 | '/docs/features/developer-tooling.html': '/docs/development-and-testing/developer-tools', 13 | '/docs/features/multiple-clients.html': '/docs/recipes/multiple-clients', 14 | '/docs/features/optimistic-ui.html': '/docs/performance/optimistic-ui', 15 | '/docs/recipes/pagination.html': '/docs/data/pagination', 16 | '/docs/recipes/prefetching.html': '/docs', 17 | '/docs/recipes/prefetching': '/docs', 18 | '/docs/recipes/server-side-rendering.html': '/docs/performance/server-side-rendering', 19 | '/docs/features/static-typing.html': '/docs', 20 | '/docs/features/caching.html': '/docs', 21 | '/docs/features/cache-updates': '/docs', 22 | '/docs/features/subscriptions': '/docs/data/subscriptions', 23 | '/docs/features/cache-updates.html': '/docs', 24 | '/docs/features/fragments.html': '/docs/data/fragments', 25 | '/docs/guides/testing.html': '/docs/development-and-testing/testing', 26 | '/docs/guides/testing': '/docs/development-and-testing/testing', 27 | '/docs/data': '/docs/data/queries', 28 | '/docs/caching': '/docs/caching/configuration', 29 | '/docs/local-state': '/docs/local-state/management', 30 | '/docs/development-and-testing': '/docs/development-and-testing/using-typescript', 31 | '/docs/performance': '/docs/performance/improving-performance', 32 | '/docs/features/error-handling': '/docs/data/error-handling', 33 | '/docs/guides/tools-and-packages': '/docs/development-and-testing/developer-tools', 34 | '/docs/basics/network-layer': '/docs/data/network', 35 | '/docs/guides/state-management': '/docs/local-state/management', 36 | '/docs/basics/caching': '/docs/caching/configuration', 37 | 38 | '/docs/features/optimistic-ui': '/docs/performance/optimistic-ui', 39 | '/docs/recipes/query-splitting': '/docs/data/queries', 40 | '/docs/features/static-typing': '/docs', 41 | '/docs/basics/mutations': '/docs/data/mutations', 42 | '/docs/recipes/pagination': '/docs/data/pagination', 43 | '/docs/basics/queries': '/docs/data/queries', 44 | '/docs/recipes/meteor': '/docs', 45 | '/docs/recipes/server-side-rendering': '/docs/performance/server-side-rendering', 46 | '/docs/features/multiple-clients': '/docs/recipes/multiple-clients', 47 | 48 | '/docs/features/fragments': '/docs/data/fragments', 49 | '/get-started': '/docs/get-started', 50 | '/docs/data/setup#using-dependency-injection': '/docs/data/queries', 51 | '/docs/data/setup.html#using-dependency-injection': '/docs/data/queries', 52 | '/docs/data/setup': '/docs/data/queries', 53 | }).map(([from, to]) => ({ 54 | source: from, 55 | destination: to, 56 | permanent: true, 57 | })), 58 | }); 59 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/tests/only-complete-data.spec.ts: -------------------------------------------------------------------------------- 1 | import { Apollo, gql, onlyCompleteData, onlyCompleteFragment, provideApollo } from 'apollo-angular'; 2 | import { map, Subject } from 'rxjs'; 3 | import { describe, expect, test } from 'vitest'; 4 | import { TestBed } from '@angular/core/testing'; 5 | import { InMemoryCache, NetworkStatus, type ObservableQuery } from '@apollo/client/core'; 6 | import { MockLink } from '@apollo/client/testing'; 7 | 8 | interface Result { 9 | user: { 10 | name: string; 11 | }; 12 | } 13 | 14 | const query = gql>` 15 | query User { 16 | user { 17 | name 18 | } 19 | } 20 | `; 21 | 22 | const fragment = gql>` 23 | fragment UserFragment on User { 24 | user { 25 | name 26 | } 27 | } 28 | `; 29 | 30 | describe('onlyCompleteData', () => { 31 | let theUser: Result['user'] | null = null; 32 | let count = 0; 33 | 34 | test('should receive only complete results', () => 35 | new Promise(done => { 36 | const b = new Subject>(); 37 | b.pipe(onlyCompleteData()).subscribe({ 38 | next: result => { 39 | count++; 40 | theUser = result.data.user; 41 | }, 42 | complete: () => { 43 | expect(count).toBe(1); 44 | expect(theUser).toEqual({ name: 'foo' }); 45 | done(); 46 | }, 47 | }); 48 | 49 | b.next({ 50 | dataState: 'partial', 51 | data: {}, 52 | loading: true, 53 | partial: true, 54 | networkStatus: NetworkStatus.loading, 55 | } satisfies ObservableQuery.Result); 56 | 57 | b.next({ 58 | dataState: 'complete', 59 | data: { user: { name: 'foo' } }, 60 | loading: false, 61 | partial: false, 62 | networkStatus: NetworkStatus.ready, 63 | } satisfies ObservableQuery.Result); 64 | 65 | b.next({ 66 | dataState: 'partial', 67 | data: {}, 68 | loading: true, 69 | partial: true, 70 | networkStatus: NetworkStatus.loading, 71 | } satisfies ObservableQuery.Result); 72 | 73 | b.complete(); 74 | })); 75 | 76 | test('should compile', () => { 77 | TestBed.configureTestingModule({ 78 | providers: [ 79 | provideApollo(() => { 80 | return { 81 | link: new MockLink([]), 82 | cache: new InMemoryCache(), 83 | }; 84 | }), 85 | ], 86 | }); 87 | 88 | const apollo = TestBed.inject(Apollo); 89 | 90 | apollo 91 | .watchQuery({ 92 | query: query, 93 | }) 94 | .valueChanges.pipe( 95 | onlyCompleteData(), 96 | map(result => result.data.user.name), 97 | ); 98 | 99 | apollo 100 | .watchFragment({ 101 | fragment: fragment, 102 | from: { 103 | __typename: 'User', 104 | id: 1, 105 | }, 106 | }) 107 | .pipe( 108 | onlyCompleteFragment(), 109 | map(result => result.data.user.name), 110 | ); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/multiple-clients.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn to use multiple Apollo clients in your Angular application with @apollo_angular. Create 4 | named clients and configure using `provideNamedApollo()` token. 5 | --- 6 | 7 | import { Callout } from '@theguild/components'; 8 | 9 | # Multiple Clients 10 | 11 | With `apollo-angular` it is possible to use multiple Apollo Clients in your application. 12 | 13 | ## Creating Clients 14 | 15 | You are already familiar with how to create a single client so it should be easy to understand it. 16 | 17 | There are few ways of creating named clients. 18 | 19 | One way is to use `Apollo.create`. Normally, you would use it like this: 20 | 21 | ```ts 22 | apollo.create(options); 23 | ``` 24 | 25 | This will define a default client but there is one optional argument. 26 | 27 | ```ts 28 | apollo.create(options, name /* optional */); 29 | ``` 30 | 31 | An example: 32 | 33 | ```ts 34 | apollo.create(defaultOptions); 35 | apollo.create(extraOptions, 'extra'); 36 | ``` 37 | 38 | Now you have the default client and one called `extra`. 39 | 40 | 41 | If you want to define a default client, simply do not use any `name` argument or set it to 42 | `default`. 43 | 44 | 45 | The other way is to use helper methods. 46 | 47 | ```ts 48 | apollo.createDefault(options); 49 | // and 50 | apollo.createNamed(name, options); 51 | ``` 52 | 53 | ## Creating Clients Using `provideNamedApollo()` 54 | 55 | In our `app.config.ts` file use `provideNamedApollo()` token to configure Apollo Client: 56 | 57 | ```ts filename="app.config.ts" 58 | import { provideNamedApollo } from 'apollo-angular'; 59 | import { HttpLink } from 'apollo-angular/http'; 60 | import { inject } from '@angular/core'; 61 | import { InMemoryCache } from '@apollo/client'; 62 | 63 | provideNamedApollo(() => { 64 | const httpLink = inject(HttpLink); 65 | 66 | return { 67 | // These settings will be saved as default client 68 | default: { 69 | link: httpLink.create({ 70 | uri: '/graphql', 71 | }), 72 | cache: new InMemoryCache(), 73 | }, 74 | // These settings will be saved by name: myAlternativeGraphQl 75 | myAlternativeGraphQl: { 76 | link: httpLink.create({ 77 | uri: '/alternative-graphql', 78 | }), 79 | cache: new InMemoryCache(), 80 | }, 81 | }; 82 | }); 83 | ``` 84 | 85 | ## Using Apollo 86 | 87 | Since we have our clients available in an app, now is the time to see how to use them. 88 | 89 | If a client is defined as the default, you can directly use all methods of the `Apollo` service. 90 | 91 | About named clients, use the method called `use(name: string)`. 92 | 93 | ```typescript 94 | import { Apollo, QueryRef } from 'apollo-angular'; 95 | import { Component } from '@angular/core'; 96 | 97 | @Component({ 98 | // ... 99 | }) 100 | export class AppComponent { 101 | feedQuery: QueryRef; 102 | 103 | constructor(apollo: Apollo) { 104 | // use default 105 | this.feedQuery = apollo.watchQuery({ 106 | /* ... */ 107 | }); 108 | 109 | // use extra client 110 | this.feedQuery = apollo.use('myAlternativeGraphQl').watchQuery({ 111 | /* ... */ 112 | }); 113 | } 114 | } 115 | ``` 116 | -------------------------------------------------------------------------------- /packages/apollo-angular/http/tests/ssr.spec.ts: -------------------------------------------------------------------------------- 1 | import { firstValueFrom } from 'rxjs'; 2 | import { filter } from 'rxjs/operators'; 3 | import { beforeEach, describe, expect, test } from 'vitest'; 4 | import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; 5 | import { ApplicationRef, Component, destroyPlatform, getPlatform, NgModule } from '@angular/core'; 6 | import { 7 | INITIAL_CONFIG, 8 | platformServer, 9 | PlatformState, 10 | renderModule, 11 | ServerModule, 12 | } from '@angular/platform-server'; 13 | import { gql } from '@apollo/client'; 14 | import { HttpLink } from '../src/http-link'; 15 | import { executeWithDefaultContext as execute } from './utils'; 16 | 17 | describe.skip('integration', () => { 18 | let doc: string; 19 | 20 | beforeEach(() => { 21 | if (getPlatform()) { 22 | destroyPlatform(); 23 | } 24 | doc = ''; 25 | }); 26 | 27 | describe('render', () => { 28 | // Mock GraphQL endpoint 29 | const query = gql` 30 | query websiteInfo { 31 | website { 32 | status 33 | } 34 | } 35 | `; 36 | const data = { 37 | website: { 38 | status: 'online', 39 | }, 40 | }; 41 | 42 | @Component({ 43 | selector: 'app', 44 | template: 'Website: {{text}}', 45 | }) 46 | class AsyncServerApp { 47 | public text = 'online'; 48 | 49 | constructor( 50 | private readonly httpLink: HttpLink, 51 | private readonly httpBackend: HttpTestingController, 52 | ) {} 53 | 54 | public ngOnInit() { 55 | execute(this.httpLink.create({ uri: 'graphql', method: 'GET' }), { 56 | query, 57 | }).subscribe((result: any) => { 58 | this.text = result.data.website.status; 59 | }); 60 | 61 | this.httpBackend 62 | .match( 63 | req => req.url === 'graphql' && req.params.get('operationName') === 'websiteInfo', 64 | )[0] 65 | .flush({ data }); 66 | } 67 | } 68 | 69 | @NgModule({ 70 | declarations: [AsyncServerApp], 71 | imports: [ServerModule], 72 | providers: [provideHttpClientTesting(), HttpLink], 73 | bootstrap: [AsyncServerApp], 74 | }) 75 | class AsyncServerModule {} 76 | 77 | test('using long form should work', async () => { 78 | const platform = platformServer([ 79 | { 80 | provide: INITIAL_CONFIG, 81 | useValue: { 82 | document: doc, 83 | }, 84 | }, 85 | ]); 86 | const moduleRef = await platform.bootstrapModule(AsyncServerModule); 87 | const applicationRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); 88 | await firstValueFrom(applicationRef.isStable.pipe(filter(isStable => isStable))); 89 | const str = platform.injector.get(PlatformState).renderToString(); 90 | 91 | expect(clearNgVersion(str)).toMatchSnapshot(); 92 | platform.destroy(); 93 | }); 94 | 95 | test('using renderModule should work', async () => { 96 | const output = await renderModule(AsyncServerModule, { document: doc }); 97 | expect(clearNgVersion(output)).toMatchSnapshot(); 98 | }); 99 | }); 100 | }); 101 | 102 | function clearNgVersion(html: string): string { 103 | return html.replace(/ng-version=\"[^"]+\"/, ''); 104 | } 105 | -------------------------------------------------------------------------------- /website/public/assets/img/logo/logo-apollo-space.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 11 | 12 | 13 | 16 | 20 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /.devcontainer/library-scripts/node-debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #------------------------------------------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 5 | #------------------------------------------------------------------------------------------------------------- 6 | 7 | # Syntax: ./node-debian.sh [directory to install nvm] [node version to install (use "none" to skip)] [non-root user] 8 | 9 | export NVM_DIR=${1:-"/usr/local/share/nvm"} 10 | export NODE_VERSION=${2:-"lts/*"} 11 | USERNAME=${3:-"vscode"} 12 | 13 | set -e 14 | 15 | if [ "$(id -u)" -ne 0 ]; then 16 | echo -e 'Script must be run a root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' 17 | exit 1 18 | fi 19 | 20 | # Treat a user name of "none" or non-existant user as root 21 | if [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then 22 | USERNAME=root 23 | fi 24 | 25 | if [ "${NODE_VERSION}" = "none" ]; then 26 | export NODE_VERSION= 27 | fi 28 | 29 | # Ensure apt is in non-interactive to avoid prompts 30 | export DEBIAN_FRONTEND=noninteractive 31 | 32 | # Install curl, apt-transport-https, tar, or gpg if missing 33 | if ! dpkg -s apt-transport-https curl ca-certificates tar > /dev/null 2>&1 || ! type gpg > /dev/null 2>&1; then 34 | if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then 35 | apt-get update 36 | fi 37 | apt-get -y install --no-install-recommends apt-transport-https curl ca-certificates tar gnupg2 38 | fi 39 | 40 | # Install yarn 41 | if type yarn > /dev/null 2>&1; then 42 | echo "Yarn already installed." 43 | else 44 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | (OUT=$(apt-key add - 2>&1) || echo $OUT) 45 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list 46 | apt-get update 47 | apt-get -y install --no-install-recommends yarn 48 | fi 49 | 50 | # Install the specified node version if NVM directory already exists, then exit 51 | if [ -d "${NVM_DIR}" ]; then 52 | echo "NVM already installed." 53 | if [ "${NODE_VERSION}" != "" ]; then 54 | su ${USERNAME} -c "source $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm clear-cache" 55 | fi 56 | exit 0 57 | fi 58 | 59 | mkdir -p ${NVM_DIR} 60 | 61 | NVM_INIT=$( 62 | cat << EOF 63 | export NVM_DIR="${NVM_DIR}" 64 | [ -s "\$NVM_DIR/nvm.sh" ] && . "\$NVM_DIR/nvm.sh" 65 | [ -s "\$NVM_DIR/bash_completion" ] && . "\$NVM_DIR/bash_completion" 66 | if [ "\$(stat -c '%U' \$NVM_DIR)" != "${USERNAME}" ]; then 67 | sudo chown -R ${USERNAME}:root \$NVM_DIR 68 | fi 69 | EOF 70 | ) 71 | 72 | echo "${NVM_INIT}" | tee -a /root/.bashrc /root/.zshrc >> /etc/skel/.bashrc 73 | # Set up non-root user if applicable 74 | if [ "${USERNAME}" != "root" ]; then 75 | # Add NVM init and add code to update NVM ownership if UID/GID changes 76 | echo "${NVM_INIT}" | tee -a /home/${USERNAME}/.bashrc >> /home/${USERNAME}/.zshrc 77 | chown ${USERNAME} ${NVM_DIR} /home/${USERNAME}/.bashrc /home/${USERNAME}/.zshrc 78 | fi 79 | 80 | # Run NVM installer as non-root if needed 81 | su ${USERNAME} -c "$( 82 | cat << EOF 83 | set -e 84 | curl -so- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash 85 | source ${NVM_DIR}/nvm.sh 86 | if [ "${NODE_VERSION}" != "" ]; then 87 | nvm alias default ${NODE_VERSION} 88 | fi 89 | nvm clear-cache 90 | EOF 91 | )" 2>&1 92 | 93 | echo "Done!" 94 | -------------------------------------------------------------------------------- /packages/apollo-angular/src/query-ref.ts: -------------------------------------------------------------------------------- 1 | import { from, Observable } from 'rxjs'; 2 | import { NgZone } from '@angular/core'; 3 | import type { 4 | ApolloClient, 5 | MaybeMasked, 6 | ObservableQuery, 7 | OperationVariables, 8 | TypedDocumentNode, 9 | } from '@apollo/client'; 10 | import { EmptyObject } from './types'; 11 | import { wrapWithZone } from './utils'; 12 | 13 | export type QueryRefFromDocument = 14 | T extends TypedDocumentNode 15 | ? QueryRef 16 | : never; 17 | 18 | export class QueryRef { 19 | public readonly valueChanges: Observable>; 20 | 21 | constructor( 22 | private readonly obsQuery: ObservableQuery, 23 | ngZone: NgZone, 24 | ) { 25 | this.valueChanges = wrapWithZone(from(this.obsQuery), ngZone); 26 | } 27 | 28 | // ObservableQuery's methods 29 | 30 | public get options(): ObservableQuery['options'] { 31 | return this.obsQuery.options; 32 | } 33 | 34 | public get variables(): ObservableQuery['variables'] { 35 | return this.obsQuery.variables; 36 | } 37 | 38 | public getCurrentResult(): ReturnType['getCurrentResult']> { 39 | return this.obsQuery.getCurrentResult(); 40 | } 41 | 42 | public refetch( 43 | variables?: Parameters['refetch']>[0], 44 | ): ReturnType['refetch']> { 45 | return this.obsQuery.refetch(variables); 46 | } 47 | 48 | public fetchMore( 49 | fetchMoreOptions: ObservableQuery.FetchMoreOptions, 50 | ): Promise>> { 51 | return this.obsQuery.fetchMore(fetchMoreOptions); 52 | } 53 | 54 | public subscribeToMore< 55 | TSubscriptionData = TData, 56 | TSubscriptionVariables extends OperationVariables = TVariables, 57 | >( 58 | options: ObservableQuery.SubscribeToMoreOptions< 59 | TData, 60 | TSubscriptionVariables, 61 | TSubscriptionData, 62 | TVariables 63 | >, 64 | ): ReturnType['subscribeToMore']> { 65 | return this.obsQuery.subscribeToMore(options); 66 | } 67 | 68 | public updateQuery( 69 | mapFn: Parameters['updateQuery']>[0], 70 | ): ReturnType['updateQuery']> { 71 | return this.obsQuery.updateQuery(mapFn); 72 | } 73 | 74 | public stopPolling(): ReturnType['stopPolling']> { 75 | return this.obsQuery.stopPolling(); 76 | } 77 | 78 | public startPolling( 79 | pollInterval: Parameters['startPolling']>[0], 80 | ): ReturnType['startPolling']> { 81 | return this.obsQuery.startPolling(pollInterval); 82 | } 83 | 84 | public setVariables( 85 | variables: Parameters['setVariables']>[0], 86 | ): ReturnType['setVariables']> { 87 | return this.obsQuery.setVariables(variables); 88 | } 89 | 90 | public reobserve( 91 | options: ObservableQuery.Options, 92 | ): ReturnType['reobserve']> { 93 | return this.obsQuery.reobserve(options); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/tests/ng-add.spec.cts: -------------------------------------------------------------------------------- 1 | import {UnitTestTree} from '@angular-devkit/schematics/testing'; 2 | import {createDependenciesMap} from '../install/index.cjs'; 3 | import {getFileContent, getJsonFile, runNgAdd} from '../utils/index.cjs'; 4 | 5 | describe('ng-add with module', () => { 6 | let tree: UnitTestTree; 7 | 8 | beforeEach(async () => { 9 | tree = await runNgAdd(false); 10 | }); 11 | 12 | it('should update package.json dependencies', async () => { 13 | const packageJsonPath = '/package.json'; 14 | expect(tree.files).toContain(packageJsonPath); 15 | 16 | const packageJson = getJsonFile(tree, packageJsonPath); 17 | const {dependencies} = packageJson; 18 | 19 | const dependenciesMap = createDependenciesMap({ 20 | project: 'my-project', 21 | graphql: '16', 22 | }); 23 | 24 | for (const dependency in dependenciesMap) { 25 | if (dependenciesMap.hasOwnProperty(dependency)) { 26 | const version = dependenciesMap[dependency]; 27 | 28 | expect(dependencies[dependency]).toBe(version); 29 | } 30 | } 31 | }); 32 | 33 | it('should add NgModule with GraphQL setup', async () => { 34 | const modulePath = '/projects/apollo/src/app/graphql.module.ts'; 35 | expect(tree.files).toContain(modulePath); 36 | 37 | const content = getFileContent(tree, modulePath); 38 | expect(content).toMatch('export class GraphQLModule'); 39 | }); 40 | 41 | it('should import the NgModule with GraphQL setup to the root module', async () => { 42 | const content = getFileContent(tree, '/projects/apollo/src/app/app.module.ts'); 43 | expect(content).toMatch(/import { GraphQLModule } from '.\/graphql.module'/); 44 | }); 45 | 46 | it('should import HttpClientModule to the root module', async () => { 47 | const content = getFileContent(tree, '/projects/apollo/src/app/app.module.ts'); 48 | 49 | expect(content).toMatch(/import { HttpClientModule } from '@angular\/common\/http'/); 50 | }); 51 | }); 52 | 53 | describe('ng-add with standalone', () => { 54 | let tree: UnitTestTree; 55 | 56 | beforeEach(async () => { 57 | tree = await runNgAdd(true); 58 | }); 59 | 60 | it('should update package.json dependencies', async () => { 61 | const packageJsonPath = '/package.json'; 62 | expect(tree.files).toContain(packageJsonPath); 63 | 64 | const packageJson = getJsonFile(tree, packageJsonPath); 65 | const {dependencies} = packageJson; 66 | 67 | const dependenciesMap = createDependenciesMap({ 68 | project: 'my-project', 69 | graphql: '16', 70 | }); 71 | 72 | for (const dependency in dependenciesMap) { 73 | if (dependenciesMap.hasOwnProperty(dependency)) { 74 | const version = dependenciesMap[dependency]; 75 | 76 | expect(dependencies[dependency]).toBe(version); 77 | } 78 | } 79 | }); 80 | 81 | it('should use `provideApollo()` to provide Apollo', async () => { 82 | const content = getFileContent(tree, '/projects/apollo/src/app/app.config.ts'); 83 | 84 | expect(content).toMatch(/provideApollo\(\(\) => {/); 85 | }); 86 | 87 | it('should import HttpClientModule to the root module', async () => { 88 | const content = getFileContent(tree, '/projects/apollo/src/app/app.config.ts'); 89 | 90 | expect(content).toMatch(/import { provideHttpClient } from '@angular\/common\/http'/); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /packages/apollo-angular/persisted-queries/tests/persisted-queries.spec.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | import { describe, expect, test, vi } from 'vitest'; 3 | import { 4 | ApolloClient, 5 | ApolloLink, 6 | execute as executeLink, 7 | gql, 8 | InMemoryCache, 9 | } from '@apollo/client'; 10 | import { createPersistedQueryLink } from '../src'; 11 | 12 | const query = gql` 13 | query heroes { 14 | heroes { 15 | name 16 | __typename 17 | } 18 | } 19 | `; 20 | const data = { heroes: [{ name: 'Foo', __typename: 'Hero' }] }; 21 | 22 | function execute(link: ApolloLink, request: ApolloLink.Request) { 23 | return executeLink(link, request, { 24 | client: new ApolloClient({ cache: new InMemoryCache(), link: ApolloLink.empty() }), 25 | }); 26 | } 27 | 28 | class MockLink extends ApolloLink { 29 | public showNotFound: boolean = true; 30 | 31 | public requester(_: any): any { 32 | return this.showNotFound 33 | ? { 34 | errors: [{ message: 'PersistedQueryNotFound' }], 35 | } 36 | : data; 37 | } 38 | 39 | public request(operation: ApolloLink.Operation) { 40 | return new Observable(observer => { 41 | const request: any = {}; 42 | 43 | if (operation.getContext().includeQuery) { 44 | request.query = operation.query; 45 | } 46 | 47 | if (operation.getContext().includeExtensions) { 48 | request.extensions = operation.extensions; 49 | } 50 | 51 | observer.next(this.requester(request)); 52 | }); 53 | } 54 | } 55 | 56 | describe('createPersistedQueryLink', () => { 57 | test('transform includeQuery and includeExtensions and has persistedQuery', () => 58 | new Promise(done => { 59 | const execLink = new MockLink(); 60 | const spyRequest = vi.spyOn(execLink, 'request').mock; 61 | const spyRequester = vi.spyOn(execLink, 'requester').mock; 62 | const link = createPersistedQueryLink({ 63 | sha256: () => 'soooo-unique', 64 | }).concat(execLink); 65 | 66 | execute(link, { 67 | query, 68 | }).subscribe(() => { 69 | const firstReq = spyRequester.calls[0][0] as any; 70 | const secondOp = spyRequest.calls[1][0] as ApolloLink.Operation; 71 | const secondReq = spyRequester.calls[1][0] as any; 72 | const secondContext = secondOp.getContext(); 73 | 74 | // should send a query only in the first request 75 | expect(firstReq.query).not.toBeDefined(); 76 | expect(secondReq.query).toBeDefined(); 77 | 78 | // should send hash in extension 79 | expect(secondOp.extensions.persistedQuery.sha256Hash).toBeDefined(); 80 | 81 | // should be compatible with apollo-angular-link-http 82 | expect(secondContext.includeQuery).toEqual(secondContext.http.includeQuery); 83 | expect(secondContext.includeExtensions).toEqual(secondContext.http.includeExtensions); 84 | 85 | // end 86 | done(); 87 | }); 88 | })); 89 | 90 | test('useGETForHashedQueries', () => 91 | new Promise(done => { 92 | const execLink = new MockLink(); 93 | const spyRequest = vi.spyOn(execLink, 'request').mock; 94 | const link = createPersistedQueryLink({ 95 | useGETForHashedQueries: true, 96 | sha256: () => 'sha256', 97 | }).concat(execLink); 98 | 99 | execute(link, { 100 | query, 101 | }).subscribe(() => { 102 | const op = spyRequest.calls[1][0] as ApolloLink.Operation; 103 | const ctx = op.getContext(); 104 | 105 | // should be compatible with apollo-angular-link-http 106 | expect(ctx.method).toEqual('GET'); 107 | 108 | // end 109 | done(); 110 | }); 111 | })); 112 | }); 113 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/src/controller.ts: -------------------------------------------------------------------------------- 1 | import { DocumentNode } from 'graphql'; 2 | import { Operation, TestOperation } from './operation'; 3 | 4 | export type MatchOperationFn = (op: Operation) => boolean; 5 | export type MatchOperation = string | DocumentNode | Operation | MatchOperationFn; 6 | 7 | /** 8 | * Controller to be injected into tests, that allows for mocking and flushing 9 | * of operations. 10 | * 11 | * 12 | */ 13 | export abstract class ApolloTestingController { 14 | /** 15 | * Search for operations that match the given parameter, without any expectations. 16 | */ 17 | public abstract match(match: MatchOperation): TestOperation[]; 18 | 19 | /** 20 | * Expect that a single operation has been made which matches the given URL, and return its 21 | * mock. 22 | * 23 | * If no such operation has been made, or more than one such operation has been made, fail with an 24 | * error message including the given operation description, if any. 25 | */ 26 | public abstract expectOne(operationName: string, description?: string): TestOperation; 27 | 28 | /** 29 | * Expect that a single operation has been made which matches the given parameters, and return 30 | * its mock. 31 | * 32 | * If no such operation has been made, or more than one such operation has been made, fail with an 33 | * error message including the given operation description, if any. 34 | */ 35 | public abstract expectOne(op: Operation, description?: string): TestOperation; 36 | 37 | /** 38 | * Expect that a single operation has been made which matches the given predicate function, and 39 | * return its mock. 40 | * 41 | * If no such operation has been made, or more than one such operation has been made, fail with an 42 | * error message including the given operation description, if any. 43 | */ 44 | public abstract expectOne(matchFn: MatchOperationFn, description?: string): TestOperation; 45 | 46 | /** 47 | * Expect that a single operation has been made which matches the given condition, and return 48 | * its mock. 49 | * 50 | * If no such operation has been made, or more than one such operation has been made, fail with an 51 | * error message including the given operation description, if any. 52 | */ 53 | public abstract expectOne(match: MatchOperation, description?: string): TestOperation; 54 | 55 | /** 56 | * Expect that no operations have been made which match the given URL. 57 | * 58 | * If a matching operation has been made, fail with an error message including the given 59 | * description, if any. 60 | */ 61 | public abstract expectNone(operationName: string, description?: string): void; 62 | 63 | /** 64 | * Expect that no operations have been made which match the given parameters. 65 | * 66 | * If a matching operation has been made, fail with an error message including the given 67 | * description, if any. 68 | */ 69 | public abstract expectNone(op: Operation, description?: string): void; 70 | 71 | /** 72 | * Expect that no operations have been made which match the given predicate function. 73 | * 74 | * If a matching operation has been made, fail with an error message including the given 75 | * description, if any. 76 | */ 77 | public abstract expectNone(matchFn: MatchOperationFn, description?: string): void; 78 | 79 | /** 80 | * Expect that no operations have been made which match the given condition. 81 | * 82 | * If a matching operation has been made, fail with an error message including the given 83 | * description, if any. 84 | */ 85 | public abstract expectNone(match: MatchOperation, description?: string): void; 86 | 87 | /** 88 | * Verify that no unmatched operations are outstanding. 89 | * 90 | * If any operations are outstanding, fail with an error message indicating which operations were not 91 | * handled. 92 | */ 93 | public abstract verify(): void; 94 | } 95 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/automatic-persisted-queries.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Improve GraphQL performance with Automatic Persisted Queries - sends a generated ID instead of 4 | query text, reducing overhead for client performance. Learn how to install and use in Angular. 5 | --- 6 | 7 | # Automatic Persisted Queries 8 | 9 | Unlike REST APIs that use a fixed URL to load data, GraphQL provides a rich query language that can 10 | be used to express the shape of application data requirements. This is a marvelous advancement in 11 | technology, but it comes at a cost: GraphQL query strings are often much longer than REST urls — in 12 | some cases by many kilobytes. 13 | 14 | In practice, we've seen GraphQL query sizes ranging well above 10 KB just for the query text. This 15 | is significant overhead when compared with a simple URL of 50-100 characters. When paired with the 16 | fact that the uplink speed from the client is typically the most bandwidth-constrained part of the 17 | chain, large queries can become bottlenecks for client performance. 18 | 19 | Automatic Persisted Queries solves this problem by sending a generated ID instead of the query text 20 | as the request. 21 | 22 | Read more about 23 | [Automatic Persisted Queries](https://blog.apollographql.com/improve-graphql-performance-with-automatic-persisted-queries-c31d27b8e6ea). 24 | 25 | ## How It Works 26 | 27 | 1. When the client makes a query, it will optimistically send a short (64-byte) cryptographic hash 28 | instead of the full query text. 29 | 2. If the backend recognizes the hash, it will retrieve the full text of the query and execute it. 30 | 3. If the backend doesn't recognize the hash, it will ask the client to send the hash and the query 31 | text to it can store them mapped together for future lookups. During this request, the backend 32 | will also fulfill the data request. 33 | 34 | ## Installation 35 | 36 | ```sh npm2yarn 37 | npm i apollo-angular 38 | ``` 39 | 40 | If you do not already have an SHA-256 based hashing function available in your application, you will 41 | need to install one separately. For example: 42 | 43 | ```sh npm2yarn 44 | npm i crypto-hash 45 | ``` 46 | 47 | This link doesn't include an SHA-256 hash function by default, to avoid forcing one as a dependency. 48 | Developers should pick the most appropriate SHA-256 function (sync or async) for their 49 | needs/environment. 50 | 51 | ## Usage 52 | 53 | Use `createPersistedQueryLink` function and put it before `HttpLink` in the link chain. 54 | 55 | ```ts filename="app.config.ts" 56 | import { provideApollo } from 'apollo-angular'; 57 | import { HttpLink } from 'apollo-angular/http'; 58 | import { createPersistedQueryLink } from 'apollo-angular/persisted-queries'; 59 | import { sha256 } from 'crypto-hash'; 60 | import { inject } from '@angular/core'; 61 | import { InMemoryCache } from '@apollo/client'; 62 | 63 | provideApollo(() => { 64 | const httpLink = inject(HttpLink); 65 | 66 | return { 67 | link: createPersistedQueryLink({ sha256 }).concat(httpLink.create({ uri: '/graphql' })), 68 | cache: new InMemoryCache(), 69 | // other options ... 70 | }; 71 | }); 72 | ``` 73 | 74 | That's it! Now your client will start sending query signatures instead of the full text resulting in 75 | improved network performance. 76 | 77 | - [Check Options](https://apollographql.com/docs/react/api/link/persisted-queries/#options) 78 | - [Read about protocol](https://apollographql.com/docs/react/api/link/persisted-queries/#protocol) 79 | - [Build time generation](https://apollographql.com/docs/react/api/link/persisted-queries/#build-time-generation) 80 | - [Learn more about Automatic Persisted Queries](https://apollographql.com/docs/react/api/link/persisted-queries/#gatsby-focus-wrapper) 81 | 82 | ## More 83 | 84 | This library is just a simple wrapper of 85 | [`@apollo/client/link/persisted-queries`](https://github.com/apollographql/apollo-client/tree/main/src/link/persisted-queries) 86 | to make it work in Angular with `apollo-angular/http` (also with batch link). Thanks to that you can 87 | use any options, any functionality that the original package provides. 88 | -------------------------------------------------------------------------------- /packages/apollo-angular/http/src/http-link.ts: -------------------------------------------------------------------------------- 1 | import { print } from 'graphql'; 2 | import { Observable } from 'rxjs'; 3 | import { HttpClient, HttpContext } from '@angular/common/http'; 4 | import { Injectable } from '@angular/core'; 5 | import { ApolloLink } from '@apollo/client'; 6 | import { pick } from './http-batch-link'; 7 | import { 8 | Body, 9 | Context, 10 | ExtractFiles, 11 | FetchOptions, 12 | HttpRequestOptions, 13 | OperationPrinter, 14 | Request, 15 | } from './types'; 16 | import { createHeadersWithClientAwareness, fetch, mergeHeaders, mergeHttpContext } from './utils'; 17 | 18 | export declare namespace HttpLink { 19 | export interface Options extends FetchOptions, HttpRequestOptions { 20 | operationPrinter?: OperationPrinter; 21 | useGETForQueries?: boolean; 22 | extractFiles?: ExtractFiles; 23 | } 24 | } 25 | 26 | // XXX find a better name for it 27 | export class HttpLinkHandler extends ApolloLink { 28 | public requester: (operation: ApolloLink.Operation) => Observable; 29 | private print: OperationPrinter = print; 30 | 31 | constructor( 32 | private readonly httpClient: HttpClient, 33 | private readonly options: HttpLink.Options, 34 | ) { 35 | super(); 36 | 37 | if (this.options.operationPrinter) { 38 | this.print = this.options.operationPrinter; 39 | } 40 | 41 | this.requester = (operation: ApolloLink.Operation) => 42 | new Observable((observer: any) => { 43 | const context: Context = operation.getContext(); 44 | 45 | let method = pick(context, this.options, 'method'); 46 | const includeQuery = pick(context, this.options, 'includeQuery'); 47 | const includeExtensions = pick(context, this.options, 'includeExtensions'); 48 | const url = pick(context, this.options, 'uri'); 49 | const withCredentials = pick(context, this.options, 'withCredentials'); 50 | const useMultipart = pick(context, this.options, 'useMultipart'); 51 | const useGETForQueries = this.options.useGETForQueries === true; 52 | const httpContext = mergeHttpContext( 53 | context.httpContext, 54 | mergeHttpContext(this.options.httpContext, new HttpContext()), 55 | ); 56 | 57 | const isQuery = operation.query.definitions.some( 58 | def => def.kind === 'OperationDefinition' && def.operation === 'query', 59 | ); 60 | 61 | if (useGETForQueries && isQuery) { 62 | method = 'GET'; 63 | } 64 | 65 | const req: Request = { 66 | method, 67 | url: typeof url === 'function' ? url(operation) : url, 68 | body: { 69 | operationName: operation.operationName, 70 | variables: operation.variables, 71 | }, 72 | options: { 73 | withCredentials, 74 | useMultipart, 75 | headers: this.options.headers, 76 | context: httpContext, 77 | }, 78 | }; 79 | 80 | if (includeExtensions) { 81 | (req.body as Body).extensions = operation.extensions; 82 | } 83 | 84 | if (includeQuery) { 85 | (req.body as Body).query = this.print(operation.query); 86 | } 87 | 88 | const headers = createHeadersWithClientAwareness(context); 89 | 90 | req.options.headers = mergeHeaders(req.options.headers, headers); 91 | 92 | const sub = fetch(req, this.httpClient, this.options.extractFiles).subscribe({ 93 | next: response => { 94 | operation.setContext({ response }); 95 | observer.next(response.body); 96 | }, 97 | error: err => observer.error(err), 98 | complete: () => observer.complete(), 99 | }); 100 | 101 | return () => { 102 | if (!sub.closed) { 103 | sub.unsubscribe(); 104 | } 105 | }; 106 | }); 107 | } 108 | 109 | public request(op: ApolloLink.Operation): Observable { 110 | return this.requester(op); 111 | } 112 | } 113 | 114 | @Injectable({ 115 | providedIn: 'root', 116 | }) 117 | export class HttpLink { 118 | constructor(private readonly httpClient: HttpClient) {} 119 | 120 | public create(options: HttpLink.Options): HttpLinkHandler { 121 | return new HttpLinkHandler(this.httpClient, options); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /website/src/pages/docs/data/error-handling.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn about handling different types of errors in your GraphQL application with Apollo Client, 4 | including GraphQL errors, server errors, transaction errors, UI errors, and Apollo Client errors. 5 | Explore error policies such as "none," "ignore," and "all," and how to set them on each request. 6 | Handle network errors with Apollo Link and the @apollo/client/link/error. Check out the "Error 7 | Handling" chapter on Apollo Client documentation for more details. 8 | --- 9 | 10 | # Error Handling 11 | 12 | Any application, from simple to complex, can have its fair share of errors. It is important to 13 | handle these errors and when possible, report these errors back to your users for information. Using 14 | GraphQL brings a new set of possible errors from the actual GraphQL response itself. With that in 15 | mind, here are a few different types of errors: 16 | 17 | - GraphQL Errors: errors in the GraphQL results that can appear alongside successful data 18 | - Server Errors: server internal errors that prevent a successful response from being formed 19 | - Transaction Errors: errors inside transaction actions like `update` on mutations 20 | - UI Errors: errors that occur in your component code 21 | - Apollo Client Errors: internal errors within the core or corresponding libraries 22 | 23 | ## Error Policies 24 | 25 | Much like `fetchPolicy`, `errorPolicy` allows you to control how GraphQL Errors from the server are 26 | sent to your UI code. By default, the error policy treats any GraphQL Errors as network errors and 27 | ends the request chain. It doesn't save any data in the cache, and renders your UI with the `error` 28 | prop to be an `ApolloError`. By changing this policy per request, you can adjust how GraphQL Errors 29 | are managed in the cache and your UI. The possible options for `errorPolicy` are: 30 | 31 | - `none`: This is the default policy to match how Apollo Client 1.0 worked. Any GraphQL Errors are 32 | treated the same as network errors and any data is ignored from the response. 33 | - `ignore`: Ignore allows you to read any data that is returned alongside GraphQL Errors, but 34 | doesn't save the errors or report them to your UI. 35 | - `all`: Using the `all` policy is the best way to notify your users of potential issues while still 36 | showing as much data as possible from your server. It saves both data and errors into the Apollo 37 | Cache so your UI can use them. 38 | 39 | You can set `errorPolicy` on each request like so: 40 | 41 | ```ts 42 | const MY_QUERY = gql` 43 | query WillFail { 44 | badField 45 | goodField 46 | } 47 | `; 48 | 49 | @Component({ 50 | // ... 51 | }) 52 | class ShowingSomeErrorsComponent { 53 | constructor(private readonly apollo: Apollo) { 54 | this.myQuery = this.apollo.watchQuery({ 55 | query: MY_QUERY, 56 | errorPolicy: 'all', 57 | }); 58 | } 59 | } 60 | ``` 61 | 62 | Any errors reported will come under an `error` prop alongside the data returned from the cache or 63 | server. 64 | 65 | ## Network Errors 66 | 67 | When using `Apollo Link`, the ability to handle network errors is way more powerful. The best way to 68 | do this is to use the `@apollo/client/link/error` to catch and handle server errors, network errors, 69 | and GraphQL errors. 70 | 71 | ```ts 72 | import { onError } from '@apollo/client/link/error'; 73 | 74 | const link = onError(({ graphQLErrors, networkError }) => { 75 | if (graphQLErrors) 76 | graphQLErrors.map(({ message, locations, path }) => 77 | console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`), 78 | ); 79 | 80 | if (networkError) console.log(`[Network error]: ${networkError}`); 81 | }); 82 | ``` 83 | 84 | Error Link takes a function that is called in the event of an error. This function is called with an 85 | object containing the following keys: 86 | 87 | - `operation`: The Operation that errored 88 | - `response`: The response from the server 89 | - `graphQLErrors`: An array of errors from the GraphQL endpoint 90 | - `networkError`: any error during the link execution or server response 91 | 92 | Ignoring errors 93 | 94 | If you want to conditionally ignore errors, you can set `response.errors = null` within the error 95 | handler: 96 | 97 | ```ts 98 | onError(({ response, operation }) => { 99 | if (operation.operationName === 'IgnoreErrorsQuery') { 100 | response.errors = null; 101 | } 102 | }); 103 | ``` 104 | -------------------------------------------------------------------------------- /website/src/pages/docs/development-and-testing/client-schema-mocking.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to use client schemas to add a new field to an existing type in your schema using mock 4 | data, with Apollo Client and GraphQL queries. 5 | --- 6 | 7 | # Mocking New Schema Capabilities 8 | 9 | Imagine we're building out a new feature in our Space Explorer app — we'd like to display a 10 | description of each rocket we can choose — but the backend support for this feature isn't going to 11 | be available for another few weeks. In keeping with schema-first design, the team has decided that 12 | we'll be adding a new field called `description` to an existing type in our schema called `Rocket`. 13 | 14 | Even though this field doesn't exist in the schema yet, we can take advantage of client schemas to 15 | document it as a client-side field. In this guide, we'll walk through a simple recipe for this 16 | technique: 17 | 18 | - declare client-side extensions to the schema using Apollo Client 19 | - enhance client resolvers with mock data 20 | - write GraphQL queries that leverage client-only types and fields 21 | 22 | ## 1. Extend Your Server Schema with a Client-Only Field 23 | 24 | Before we can include this data in the UI, we'll need to define a client schema that extends our 25 | server schema. We'll start by constructing an instance of ApolloClient with a few small additions. 26 | Pass in `typeDefs` with extensions to the schema and `resolvers` which actually provide the mock 27 | data: 28 | 29 | ```ts filename="app.config.ts" 30 | import { provideApollo } from 'apollo-angular'; 31 | import { HttpLink } from 'apollo-angular/http'; 32 | import { inject } from '@angular/core'; 33 | import { InMemoryCache } from '@apollo/client'; 34 | 35 | const typeDefs = gql` 36 | extend type Rocket { 37 | description: String! 38 | } 39 | `; 40 | 41 | const resolvers = { 42 | Rocket: { 43 | description: () => 'A boilerplate standard space rocket', 44 | }, 45 | }; 46 | 47 | provideApollo(() => { 48 | const httpLink = inject(HttpLink); 49 | 50 | return { 51 | link: httpLink.create({ uri: '/graphql' }), 52 | cache: new InMemoryCache(), 53 | typeDefs, 54 | resolvers, 55 | // other options ... 56 | }; 57 | }); 58 | ``` 59 | 60 | Documenting your client-side API in Schema Definition Language is incredibly valuable, as other 61 | developers can easily see what client state is available in your app. Developers familiar with 62 | GraphQL schemas should quickly be able to understand how to query for these fields in other places 63 | throughout your app. 64 | 65 | ## 2. Introduce Richer Mock Data 66 | 67 | Now that we have a basic resolver, we might find that during testing it's a bit boring to show the 68 | same boilerplate text every time. In fact we might want to test different lengths of text to make 69 | sure our layout still looks good. Introducing a mock data helper library such as 70 | [faker.js](https://github.com/marak/Faker.js/) can help keep the mock data varied while testing. We 71 | can incorporate it easily into this workflow: 72 | 73 | ```ts 74 | import faker from 'faker/locale/en'; 75 | 76 | // returns either 1 or 2 latin sentences 77 | const oneOrTwoSentences = () => faker.lorem.sentences(Math.random() < 0.5 ? 1 : 2); 78 | 79 | const resolvers = { 80 | Rocket: { 81 | description: () => oneOrTwoSentences(), 82 | }, 83 | }; 84 | ``` 85 | 86 | ## 3. Query the Mocked Field with the `@client` Directive 87 | 88 | Now, you’re ready to query your new field inside the `RocketDetails` component. Just add your new 89 | field to the query and specify the `@client` directive, and start using it in your UI. 90 | 91 | ```ts 92 | const GET_ROCKET_DETAILS = gql` 93 | query RocketDetails($rocketId: ID!) { 94 | rocket(id: $rocketId) { 95 | type 96 | description @client 97 | } 98 | } 99 | `; 100 | 101 | @Component({ 102 | // ... 103 | }) 104 | export class RocketDetailsComponent { 105 | @Input() rocketId: number; 106 | 107 | constructor(apollo: Apollo) { 108 | this.apollo.watchQuery({ 109 | query: GET_ROCKET_DETAILS, 110 | variables: { rocketId }, 111 | }); 112 | } 113 | } 114 | ``` 115 | 116 | ## 4. Toggle on "Real" Data 117 | 118 | Once the feature is ready on the backend, just remove the `@client` directive from your query. You 119 | should now be able to see your real production data returned instead. It's probably a good idea to 120 | clean up any unused client schema and resolvers at this time. 121 | -------------------------------------------------------------------------------- /website/src/pages/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | What is Apollo Angular and what does it do?. Learn how to use Apollo Angular to build your next 4 | app. 5 | --- 6 | 7 | # Introduction 8 | 9 | ## Write Queries, Not Code 10 | 11 | [Apollo Client](https://github.com/apollographql/apollo-client) is a flexible, community driven, 12 | GraphQL client for JavaScript. It is designed from the ground up to make it easy to build UI 13 | components that fetch data with GraphQL. To get the most value out of Apollo Client, you should use 14 | it with one of its view layer integrations. To get started with the Angular integration, go to 15 | [Get Started](./get-started). 16 | 17 | 1. **Incrementally adoptable**, so that you can drop it into an existing JavaScript app and start 18 | using GraphQL for just part of your UI. 19 | 1. **Universally compatible**, so that Apollo works with any build setup, any GraphQL server, and 20 | any GraphQL schema. 21 | 1. **Simple to get started with**, so you can start loading data right away and learn about advanced 22 | features later. 23 | 1. **Inspectable and understandable**, so that you can have great developer tools to understand 24 | exactly what is happening in your app. 25 | 1. **Built for interactive apps**, so your users can make changes and see them reflected in the UI 26 | immediately. 27 | 1. **Small and flexible**, so you don't get stuff you don't need. The core is under 12kb compressed. 28 | 1. **Community driven**, because Apollo is driven by the community and serves a variety of use 29 | cases. Everything is planned and developed in the open. 30 | 31 | These docs will help you to go from getting started with Apollo to becoming an expert! 32 | 33 | ## Getting Started 34 | 35 | The docs are divided into three distinct sections to make it easy to find your way around: 36 | 37 | 1. **Basics**, which outline the why and how of using Apollo Angular to build your application. 38 | 1. **Features**, which showcase some advanced capabilities of Apollo Angular that your app may need. 39 | 1. **Recipes**, to isolate and explain how to do common patterns. 40 | 41 | Getting started is as simple as installing a few libraries from npm! The 42 | [Get Started](./get-started) is a good place to start your adventure with Apollo! 43 | 44 | ### Compatible Tools 45 | 46 | We want you to love working with Apollo Angular, so we work extra hard to make sure it works with 47 | the client or server tools you're already using! The maintainers and contributors focus on solving 48 | the hard problems around GraphQL caching, request management, and UI updating, and we want that to 49 | be available to anyone regardless of their technical requirements and preferences for other parts of 50 | the app. 51 | 52 | ### The Angular Toolbox 53 | 54 | Apollo is lovingly designed to work nicely with all the tools used by today's Angular developers. 55 | Here are some in particular: 56 | 57 | - **Angular Schematics**: Apollo Angular supports `ng-add` and `ng-update` 58 | - **NativeScript**: Apollo works out of the box in NativeScript. 59 | - **Angular Router**: Apollo Client is completely router-independent, which means you can use it 60 | with any version of [Angular Router](https://github.com/angular/angular) or any other routing 61 | library for Angular. It's even easy to set up 62 | [server-side rendering](./performance/improving-performance). 63 | - **Ionic**: Apollo works great with [Ionic Framework](http://ionicframework.com) apps written in 64 | Angular 65 | 66 | If you have a favorite Angular tool, and something in Apollo makes it difficult to integrate, please 67 | open an issue and let's work together to make it work nicely and add it to the list! 68 | 69 | ### GraphQL Servers 70 | 71 | We believe that using GraphQL should be easy and fun. One of the ways Apollo is designed for this is 72 | that if you can write your query in GraphiQL, it'll work with Apollo Client! Because it doesn't 73 | assume anything beyond the official GraphQL specification, Apollo works with every GraphQL server 74 | implementation, for _every_ language. It doesn't impose any requirements on your schema either! If 75 | you can send a query to a standard GraphQL server, Apollo can handle it. You can find a list of 76 | GraphQL server implementations on [graphql.org](http://graphql.org/code/#server-libraries). 77 | 78 | ### Other JavaScript + Native Platforms 79 | 80 | This documentation site is written with examples using Angular, but Apollo has an implementation for 81 | every client platform: 82 | 83 | - JavaScript 84 | - [React](https://apollographql.com/docs/react) 85 | - [Vue](https://github.com/Akryum/vue-apollo) 86 | - [Ember](https://github.com/bgentry/ember-apollo-client) 87 | - [Polymer](https://github.com/aruntk/polymer-apollo) 88 | - Native mobile 89 | - [Native iOS with Swift](https://apollographql.com/docs/ios) 90 | - [Native Android with Java](https://github.com/apollographql/apollo-android) 91 | -------------------------------------------------------------------------------- /packages/apollo-angular/schematics/install/index.cts: -------------------------------------------------------------------------------- 1 | import { dirname } from 'path'; 2 | import { 3 | apply, 4 | chain, 5 | mergeWith, 6 | move, 7 | Rule, 8 | SchematicContext, 9 | template, 10 | Tree, 11 | url, 12 | } from '@angular-devkit/schematics'; 13 | import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; 14 | import { addRootProvider } from '@schematics/angular/utility'; 15 | import { getAppModulePath, isStandaloneApp } from '@schematics/angular/utility/ng-ast-utils'; 16 | import { getMainFilePath } from '@schematics/angular/utility/standalone/util'; 17 | import { addModuleImportToRootModule } from '../utils/ast.cjs'; 18 | import { getJsonFile } from '../utils/index.cjs'; 19 | import { Schema } from './schema.cjs'; 20 | 21 | export function factory(options: Schema): Rule { 22 | return chain([ 23 | addDependencies(options), 24 | addSetupFiles(options), 25 | importHttpClient(options), 26 | importSetup(options), 27 | ]); 28 | } 29 | 30 | export function createDependenciesMap(options: Schema): Record { 31 | return { 32 | 'apollo-angular': '^9.0.0', 33 | '@apollo/client': '^4.0.1', 34 | graphql: `^${options.graphql ?? '16.0.0'}`, 35 | }; 36 | } 37 | 38 | /** 39 | * Add all necessary node packages 40 | * as dependencies in the package.json 41 | * and installs them by running `npm install`. 42 | */ 43 | function addDependencies(options: Schema): Rule { 44 | return (host: Tree, context: SchematicContext) => { 45 | const packageJsonPath = 'package.json'; 46 | const packageJson = getJsonFile(host, packageJsonPath); 47 | 48 | packageJson.dependencies = packageJson.dependencies || {}; 49 | 50 | const dependenciesMap = createDependenciesMap(options); 51 | for (const dependency in dependenciesMap) { 52 | if (dependenciesMap.hasOwnProperty(dependency)) { 53 | const version = dependenciesMap[dependency]; 54 | if (!packageJson.dependencies[dependency]) { 55 | packageJson.dependencies[dependency] = version; 56 | } 57 | } 58 | } 59 | 60 | // save the changed file 61 | host.overwrite(packageJsonPath, JSON.stringify(packageJson, null, 2)); 62 | 63 | // schedule `npm install` 64 | context.addTask(new NodePackageInstallTask()); 65 | 66 | return host; 67 | }; 68 | } 69 | 70 | function addSetupFiles(options: Schema): Rule { 71 | return async (host: Tree) => { 72 | const mainPath = await getMainFilePath(host, options.project); 73 | const appModuleDirectory = dirname(mainPath) + '/app'; 74 | if (isStandaloneApp(host, mainPath)) { 75 | const templateSource = apply(url('./files/standalone'), [ 76 | template({ 77 | endpoint: options.endpoint, 78 | }), 79 | move(appModuleDirectory), 80 | ]); 81 | 82 | return mergeWith(templateSource); 83 | } else { 84 | const appModulePath = getAppModulePath(host, mainPath); 85 | const appModuleDirectory = dirname(appModulePath); 86 | const templateSource = apply(url('./files/module'), [ 87 | template({ 88 | endpoint: options.endpoint, 89 | }), 90 | move(appModuleDirectory), 91 | ]); 92 | 93 | return mergeWith(templateSource); 94 | } 95 | }; 96 | } 97 | 98 | function importSetup(options: Schema): Rule { 99 | return async (host: Tree) => { 100 | const mainPath = await getMainFilePath(host, options.project); 101 | if (isStandaloneApp(host, mainPath)) { 102 | return addRootProvider(options.project, ({ code, external }) => { 103 | return code`${external('provideApollo', 'apollo-angular')}(() => { 104 | const httpLink = ${external('inject', '@angular/core')}(${external('HttpLink', 'apollo-angular/http')}); 105 | 106 | return { 107 | link: httpLink.create({ 108 | uri: '<%= endpoint %>', 109 | }), 110 | cache: new ${external('InMemoryCache', '@apollo/client')}(), 111 | }; 112 | })`; 113 | }); 114 | } else { 115 | return addModuleImportToRootModule( 116 | host, 117 | 'GraphQLModule', 118 | './graphql.module', 119 | options.project, 120 | ); 121 | } 122 | }; 123 | } 124 | 125 | function importHttpClient(options: Schema): Rule { 126 | return async (host: Tree) => { 127 | const mainPath = await getMainFilePath(host, options.project); 128 | if (isStandaloneApp(host, mainPath)) { 129 | return addRootProvider(options.project, ({ code, external }) => { 130 | return code`${external('provideHttpClient', '@angular/common/http')}()`; 131 | }); 132 | } else { 133 | return addModuleImportToRootModule( 134 | host, 135 | 'HttpClientModule', 136 | '@angular/common/http', 137 | options.project, 138 | ); 139 | } 140 | }; 141 | } 142 | -------------------------------------------------------------------------------- /website/src/pages/docs/performance/optimistic-ui.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Optimistic UI speeds up UI response time by simulating mutations and updating the UI before 4 | receiving a response from the server. This code shows how to use optimistic UI with GraphQL and 5 | Apollo, including updating an existing object and adding to a list. 6 | --- 7 | 8 | # Optimistic UI 9 | 10 | As explained in the [mutations](../data/mutations#optimistic-ui) section, optimistic UI is a pattern 11 | that you can use to simulate the results of a mutation and update the UI even before receiving a 12 | response from the server. Once the response is received from the server, optimistic result is thrown 13 | away and replaced with the actual result. 14 | 15 | Optimistic UI provides an easy way to make your UI respond much faster, while ensuring that the data 16 | becomes consistent with the actual response when it arrives. 17 | 18 | ## Basic Optimistic UI 19 | 20 | Let's say we have an "edit comment" mutation, and we want the UI to update immediately when the user 21 | submits the mutation, instead of waiting for the server response. This is what the 22 | `optimisticResponse` parameter to the `mutate` method provides. 23 | 24 | The main way to get GraphQL data into your UI components with Apollo is to use a query, so if we 25 | want our optimistic response to update the UI, we have to make sure to return an optimistic response 26 | that will update the correct query result. 27 | 28 | Here's what this looks like in the code: 29 | 30 | ```ts 31 | const updateComment = gql` 32 | mutation updateComment($commentId: ID!, $commentContent: String!) { 33 | updateComment(commentId: $commentId, commentContent: $commentContent) { 34 | id 35 | __typename 36 | content 37 | } 38 | } 39 | `; 40 | 41 | @Component({ 42 | // ... 43 | }) 44 | class AppComponent { 45 | submit({ commentId, commentContent }) { 46 | this.apollo 47 | .mutate({ 48 | variables: { commentId, commentContent }, 49 | optimisticResponse: { 50 | __typename: 'Mutation', 51 | updateComment: { 52 | id: commentId, 53 | __typename: 'Comment', 54 | content: commentContent, 55 | }, 56 | }, 57 | }) 58 | .subscribe(); 59 | } 60 | } 61 | ``` 62 | 63 | We select `id` and `__typename` because that's what our Type Policies use to determine a globally 64 | unique object ID. We need to make sure to provide the right values for those fields, so that Apollo 65 | knows what object we are referring to. 66 | 67 | ## Adding to a List 68 | 69 | In the example above, we showed how to seamlessly edit an existing object in the store with an 70 | optimistic mutation result. However, many mutations don't just update an existing object in the 71 | store, but they insert a new one. 72 | 73 | In that case we need to specify how to integrate the new data into existing queries, and thus our 74 | UI. You can read in detail about how to do that in the article about 75 | [controlling the store](../caching/interaction) -- in particular, we can use the `update` method to 76 | insert a result into an existing query's result set. `update` works exactly the same way for 77 | optimistic results and the real results returned from the server. 78 | 79 | Here is a concrete example from GitHunt, which inserts a comment into an existing list of comments. 80 | 81 | ```ts 82 | import CommentAppQuery from '../queries/CommentAppQuery'; 83 | 84 | const SUBMIT_COMMENT_MUTATION = gql` 85 | mutation submitComment($repoFullName: String!, $commentContent: String!) { 86 | submitComment(repoFullName: $repoFullName, commentContent: $commentContent) { 87 | postedBy { 88 | login 89 | html_url 90 | } 91 | createdAt 92 | content 93 | } 94 | } 95 | `; 96 | 97 | @Component({ 98 | // ... 99 | }) 100 | class AppComponent { 101 | submit({ repoFullName, commentContent }) { 102 | this.apollo 103 | .mutate({ 104 | variables: { repoFullName, commentContent }, 105 | optimisticResponse: { 106 | __typename: 'Mutation', 107 | submitComment: { 108 | __typename: 'Comment', 109 | postedBy: ownProps.currentUser, 110 | createdAt: +new Date(), 111 | content: commentContent, 112 | }, 113 | }, 114 | update(proxy, { data: { submitComment } }) { 115 | // Read the data from our cache for this query. 116 | const data = proxy.readQuery({ query: CommentAppQuery }); 117 | // Add our comment from the mutation to the end. 118 | data.comments.push(submitComment); 119 | // Write our data back to the cache. 120 | proxy.writeQuery({ query: CommentAppQuery, data }); 121 | }, 122 | }) 123 | .subscribe(); 124 | } 125 | } 126 | ``` 127 | -------------------------------------------------------------------------------- /website/src/pages/docs/development-and-testing/using-typescript.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Learn how to use TypeScript with Apollo to create a strongly-typed GraphQL API in your Angular 4 | app. Type definitions can improve development, and Apollo supports this with definitions for 5 | @apollo/client and apollo-angular. Explore how to type results and variables in your queries and 6 | see how it can improve your application and developer experience. 7 | --- 8 | 9 | # TypeScript 10 | 11 | As your application grows, you may find it helpful to include a type system to assist in 12 | development. Apollo supports type definitions for TypeScript system. Both `@apollo/client` and 13 | `apollo-angular` ship with definitions in their npm packages, so installation should be done for you 14 | after the libraries are included in your project. 15 | 16 | ## Operation Result 17 | 18 | The most common need when using type systems with GraphQL is to type the results of an operation. 19 | Given that a GraphQL server's schema is strongly typed, we can even generate TypeScript definitions 20 | automatically using a tool like 21 | [Graphql Code Generator](https://graphql-code-generator.com/docs/plugins/typescript-apollo-angular). 22 | In these docs however, we will be writing result types manually. 23 | 24 | Since the result of a query will be sent to the component or service, we want to be able to tell our 25 | type system the shape of it. Here is an example setting types for an operation using TypeScript: 26 | 27 | ```ts 28 | import { Apollo, gql } from 'apollo-angular'; 29 | 30 | type Hero = { 31 | name: string; 32 | id: string; 33 | appearsIn: string[]; 34 | friends: Hero[]; 35 | }; 36 | 37 | type Response = { 38 | hero: Hero; 39 | }; 40 | 41 | const HERO_QUERY = gql` 42 | query GetCharacter($episode: Episode!) { 43 | hero(episode: $episode) { 44 | name 45 | id 46 | friends { 47 | name 48 | id 49 | appearsIn 50 | } 51 | } 52 | } 53 | `; 54 | 55 | @Component({ 56 | // ... 57 | }) 58 | class AppComponent { 59 | response; 60 | constructor(apollo: Apollo) { 61 | apollo 62 | .watchQuery({ 63 | query: HERO_QUERY, 64 | variables: { episode: 'JEDI' }, 65 | }) 66 | .valueChanges.subscribe(result => { 67 | console.log(result.data.hero); // no TypeScript errors 68 | }); 69 | } 70 | } 71 | ``` 72 | 73 | Without specifying a Generic Type for `Apollo.watchQuery`, TypeScript would throw an error saying 74 | that `hero` property does not exist in `result.data` object (it is an `Object` by default). 75 | 76 | ## Options 77 | 78 | To make integration between Apollo and Angular even more statically typed you can define the shape 79 | of variables (in query, watchQuery and mutate methods). Here is an example setting the type of 80 | variables: 81 | 82 | ```ts 83 | import { Apollo, gql } from 'apollo-angular'; 84 | 85 | type Hero = { 86 | name: string; 87 | id: string; 88 | appearsIn: string[]; 89 | friends: Hero[]; 90 | }; 91 | 92 | type Response = { 93 | hero: Hero; 94 | }; 95 | 96 | type Variables = { 97 | episode: string; 98 | }; 99 | 100 | const HERO_QUERY = gql` 101 | query GetCharacter($episode: Episode!) { 102 | hero(episode: $episode) { 103 | name 104 | id 105 | friends { 106 | name 107 | id 108 | appearsIn 109 | } 110 | } 111 | } 112 | `; 113 | 114 | @Component({ 115 | // ... 116 | }) 117 | class AppComponent { 118 | constructor(apollo: Apollo) { 119 | apollo 120 | .watchQuery({ 121 | query: HERO_QUERY, 122 | variables: { episode: 'JEDI' }, // controlled by TypeScript 123 | }) 124 | .valueChanges.subscribe(result => { 125 | console.log(result.data.hero); 126 | }); 127 | } 128 | } 129 | ``` 130 | 131 | With this addition, the entirety of the integration between Apollo and Angular can be statically 132 | typed. When combined with the strong tooling each system provides, it can make for a much improved 133 | application and developer experience. 134 | 135 | ## Other Usage 136 | 137 | It is not only `Apollo` service where you can use generic types for Options and Variables. Same 138 | logic applies to `QueryRef` object. 139 | 140 | ```ts 141 | import { QueryRef } from 'apollo-angular'; 142 | 143 | type Hero = { 144 | name: string; 145 | id: string; 146 | appearsIn: string[]; 147 | friends: Hero[]; 148 | }; 149 | 150 | type Response = { 151 | hero: Hero; 152 | }; 153 | 154 | type Variables = { 155 | episode: string; 156 | }; 157 | 158 | @Component({ 159 | // ... 160 | }) 161 | class AppComponent { 162 | heroQuery: QueryRef; 163 | 164 | changeEpisode(episode: string) { 165 | this.heroQuery.setVariables({ 166 | episode: 'JEDI', 167 | }); 168 | } 169 | } 170 | ``` 171 | -------------------------------------------------------------------------------- /packages/apollo-angular/testing/tests/operation.spec.ts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, test } from 'vitest'; 2 | import { ApolloLink, gql } from '@apollo/client'; 3 | import { ApolloTestingBackend } from '../src/backend'; 4 | import { buildOperationForLink, executeWithDefaultContext as execute } from './utils'; 5 | 6 | const testQuery = gql` 7 | query allHeroes { 8 | heroes { 9 | name 10 | } 11 | } 12 | `; 13 | const testSubscription = gql` 14 | subscription newHeroes { 15 | heroes { 16 | name 17 | } 18 | } 19 | `; 20 | const testMutation = gql` 21 | mutation addHero($hero: String!) { 22 | addHero(hero: $hero) { 23 | name 24 | } 25 | } 26 | `; 27 | 28 | describe('TestOperation', () => { 29 | let mock: ApolloTestingBackend; 30 | let link: ApolloLink; 31 | 32 | beforeEach(() => { 33 | mock = new ApolloTestingBackend(); 34 | link = new ApolloLink(op => 35 | mock.handle({ 36 | ...op, 37 | clientName: 'default', 38 | }), 39 | ); 40 | }); 41 | 42 | test('accepts a null body', () => 43 | new Promise(done => { 44 | const operation = buildOperationForLink(testQuery, {}); 45 | 46 | execute(link, operation).subscribe(result => { 47 | expect(result).toBeNull(); 48 | done(); 49 | }); 50 | 51 | mock.expectOne(testQuery).flush(null!); 52 | })); 53 | 54 | test('should accepts data for flush operation', () => 55 | new Promise(done => { 56 | const operation = buildOperationForLink(testQuery, {}); 57 | 58 | execute(link, operation).subscribe(result => { 59 | expect(result).toEqual({ 60 | data: { 61 | heroes: [], 62 | }, 63 | }); 64 | 65 | done(); 66 | }); 67 | 68 | mock.expectOne(testQuery).flushData({ 69 | heroes: [], 70 | }); 71 | })); 72 | 73 | test('should leave the operation open for a subscription', () => 74 | new Promise(done => { 75 | const operation = buildOperationForLink(testSubscription, {}); 76 | const emittedResults: ApolloLink.Result[] = []; 77 | 78 | execute(link, operation).subscribe({ 79 | next(result) { 80 | emittedResults.push(result); 81 | }, 82 | complete() { 83 | expect(emittedResults).toEqual([ 84 | { 85 | data: { 86 | heroes: ['first Hero'], 87 | }, 88 | }, 89 | { 90 | data: { 91 | heroes: ['second Hero'], 92 | }, 93 | }, 94 | ]); 95 | done(); 96 | }, 97 | }); 98 | 99 | const testOperation = mock.expectOne(testSubscription); 100 | 101 | testOperation.flushData({ 102 | heroes: ['first Hero'], 103 | }); 104 | 105 | testOperation.flushData({ 106 | heroes: ['second Hero'], 107 | }); 108 | 109 | testOperation.complete(); 110 | })); 111 | 112 | test('should close the operation after a query', () => 113 | new Promise(done => { 114 | const operation = buildOperationForLink(testQuery, {}); 115 | const emittedResults: ApolloLink.Result[] = []; 116 | 117 | execute(link, operation).subscribe({ 118 | next(result) { 119 | emittedResults.push(result); 120 | }, 121 | complete() { 122 | expect(emittedResults).toEqual([ 123 | { 124 | data: { 125 | heroes: ['first Hero'], 126 | }, 127 | }, 128 | ]); 129 | done(); 130 | }, 131 | }); 132 | 133 | const testOperation = mock.expectOne(testQuery); 134 | 135 | testOperation.flushData({ 136 | heroes: ['first Hero'], 137 | }); 138 | 139 | testOperation.flushData({ 140 | heroes: ['second Hero'], 141 | }); 142 | })); 143 | 144 | test('should close the operation after a mutation', () => 145 | new Promise(done => { 146 | const operation = buildOperationForLink(testMutation, { hero: 'firstHero' }); 147 | const emittedResults: ApolloLink.Result[] = []; 148 | 149 | execute(link, operation).subscribe({ 150 | next(result) { 151 | emittedResults.push(result); 152 | }, 153 | complete() { 154 | expect(emittedResults).toEqual([ 155 | { 156 | data: { 157 | heroes: ['first Hero'], 158 | }, 159 | }, 160 | ]); 161 | done(); 162 | }, 163 | }); 164 | 165 | const testOperation = mock.expectOne(testMutation); 166 | 167 | testOperation.flushData({ 168 | heroes: ['first Hero'], 169 | }); 170 | 171 | testOperation.flushData({ 172 | heroes: ['second Hero'], 173 | }); 174 | })); 175 | }); 176 | -------------------------------------------------------------------------------- /website/src/pages/docs/recipes/webpack.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | How to use the Webpack Loader. Learn how to load GraphQL queries over `.graphql` files using 4 | Webpack. 5 | --- 6 | 7 | # Webpack Loader 8 | 9 | You can load GraphQL queries over `.graphql` files using Webpack. The package 10 | [`@graphql-tools/webpack-loader`](https://www.npmjs.com/package/@graphql-tools/webpack-loader) comes 11 | with a loader easy to setup and with some benefits: 12 | 13 | 1. Do not process GraphQL ASTs on client-side 14 | 1. Enable queries to be separated from logic 15 | 16 | In the example below, we create a new file called `currentUser.graphql`: 17 | 18 | ```graphql filename="currentUser.graphql" 19 | query CurrentUserForLayout { 20 | currentUser { 21 | login 22 | avatar_url 23 | } 24 | } 25 | ``` 26 | 27 | You can load this file adding a rule in your webpack config file: 28 | 29 | ```ts 30 | rules: [ 31 | { 32 | test: /\.(graphql|gql)$/, 33 | exclude: /node_modules/, 34 | loader: '@graphql-tools/webpack-loader', 35 | }, 36 | ]; 37 | ``` 38 | 39 | As you can see, `.graphql` or `.gql` files will be parsed whenever imported: 40 | 41 | ```ts 42 | import { Apollo } from 'apollo-angular'; 43 | import { Component } from '@angular/core'; 44 | import currentUserQuery from './currentUser.graphql'; 45 | 46 | @Component({ 47 | // ... 48 | }) 49 | class ProfileComponent { 50 | constructor(apollo: Apollo) { 51 | apollo.query({ query: currentUserQuery }).subscribe(result => { 52 | // ... 53 | }); 54 | } 55 | } 56 | ``` 57 | 58 | ## Optional: Install and Configure a Custom webpack Configuration (When Using Angular CLI) 59 | 60 | Install `@angular-builders/custom-webpack`: 61 | 62 | ```sh npm2yarn 63 | npm i @angular-builders/custom-webpack 64 | ``` 65 | 66 | Then create a webpack configuration file `webpack.config.js` in your application root containing 67 | your Webpack configuration (as listed above): 68 | 69 | ```js filename="webpack.config.js" 70 | module.exports = { 71 | module: { 72 | rules: [ 73 | { 74 | test: /\.(graphql|gql)$/, 75 | exclude: /node_modules/, 76 | loader: '@graphql-tools/webpack-loader', 77 | }, 78 | ], 79 | }, 80 | }; 81 | ``` 82 | 83 | After that, create your type-definition for your `.graphql` files, so TypeScript will transform them 84 | into importable objects with `src/@types/graphql.d.ts`: 85 | 86 | ```ts filename="graphql.d.ts" 87 | declare module '*.graphql' { 88 | import { DocumentNode } from 'graphql'; 89 | const schema: DocumentNode; 90 | 91 | export = schema; 92 | } 93 | ``` 94 | 95 | Next, update your TSConfig: 96 | 97 | ```jsonc filename="tsconfig.json" 98 | { 99 | // ... 100 | "files": [ 101 | // ... 102 | "src/@types/graphql.d.ts", 103 | ], 104 | "compilerOptions": { 105 | "typeRoots": [ 106 | // ... 107 | "src/@types", 108 | ], 109 | }, 110 | } 111 | ``` 112 | 113 | Finally, you have to manipulate your `angular.json` to accept your customized webpack configuration: 114 | 115 | ```jsonc filename="angular.json" 116 | { 117 | // ... 118 | "projects": { 119 | "": { 120 | // ... 121 | "architect": { 122 | "build": { 123 | // ... 124 | "builder": "@angular-builders/custom-webpack:browser", 125 | "options": { 126 | "customWebpackConfig": { 127 | "path": "./webpack.config.js", 128 | "replaceDuplicatePlugins": true, 129 | }, 130 | }, 131 | }, 132 | "serve": { 133 | // ... 134 | "builder": "@angular-builders/custom-webpack:dev-server", 135 | }, 136 | }, 137 | }, 138 | }, 139 | } 140 | ``` 141 | 142 | _(Based on 143 | [How to resolve import for the .graphql file with typescript and webpack](https://dev.to/open-graphql/how-to-resolve-import-for-the-graphql-file-with-typescript-and-webpack-35lf))_ 144 | 145 | ### Jest 146 | 147 | [Jest](https://facebook.github.io/jest/) can't use the Webpack loaders. To make the same 148 | transformation work in Jest, use 149 | [`@graphql-tools/jest-transform`](https://npmjs.com/package/@graphql-tools/jest-transform). 150 | 151 | ## Fragments 152 | 153 | You can use and include fragments in `.graphql` files and have webpack include those dependencies 154 | for you, similar to how you would use fragments and queries with the gql tag in plain JavaScript. 155 | 156 | ```graphql 157 | #import "./UserInfoFragment.graphql" 158 | 159 | query CurrentUserForLayout { 160 | currentUser { 161 | ...UserInfo 162 | } 163 | } 164 | ``` 165 | 166 | See how we import the UserInfo fragment from another `.graphql` file (same way you'd import modules 167 | in JavaScript). 168 | 169 | And here's an example of defining the fragment in another `.graphql` file. 170 | 171 | ```graphql 172 | fragment UserInfo on User { 173 | login 174 | avatar_url 175 | } 176 | ``` 177 | -------------------------------------------------------------------------------- /website/src/pages/docs/development-and-testing/developer-tools.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | Discover essential developer tools for Apollo Angular including GraphQL Inspector, GraphQL 4 | Codegen, and more. Improve and maintain your GraphQL API with these powerful tools. 5 | --- 6 | 7 | # Developer Tools 8 | 9 | Thank you to our amazing community members who have created tools and packages around Apollo 10 | Angular! If you’ve built something and would like it to be featured, please send a pull request to 11 | add it to the list. 12 | 13 | ## Breaking Change Detection with GraphQL Inspector 14 | 15 | [GraphQL Inspector](https://graphql-inspector.com) is a set of tools to help you better maintain and 16 | improve GraphQL API as well as GraphQL consumers. 17 | 18 | GraphQL Inspector outputs a list of changes between two GraphQL schemas. Every change is precisely 19 | explained and marked as breaking, non-breaking or dangerous. It will help you validate documents and 20 | fragments against a schema and even find similar or duplicated types. 21 | 22 | Use GraphQL Inspector however you like: 23 | 24 | - Command Line Tool 25 | - [GitHub Application](https://graphql-inspector.com/docs/products/github) 26 | - [GitHub Action](https://graphql-inspector.com/docs/products/action) 27 | - Programmatic API 28 | 29 | The way GraphQL Inspector is built enables you to use everything through a CLI or each feature 30 | separately using one of many available packages. There's also a GitHub Application that you can 31 | install and use within seconds. If you're a fan of GitHub Actions, we got you covered too. 32 | 33 | ![Breaking Change Detection with GraphQL Inspector](https://graphql-inspector.com/assets/img/github/app-action.jpg) 34 | 35 | ## Code Generation with GraphQL Codegen 36 | 37 | A tool to generate a ready to use in your component, strongly typed Angular services, for every 38 | defined query, mutation or subscription. 39 | 40 | To learn more about the tool, please read the 41 | ["Apollo-Angular 1.2  —  using GraphQL in your apps just got a whole lot easier!"](https://medium.com/the-guild/apollo-angular-code-generation-7903da1f8559) 42 | article. 43 | 44 | More about Query, Mutation, Subscription services in 45 | ["Query, Mutation, Subscription services"](../data/services) chapter of Apollo Angular 46 | documentation. 47 | 48 | [Read documentation](https://graphql-code-generator.com/docs/plugins/typescript-apollo-angular). 49 | 50 | ```sh npm2yarn 51 | npm i -D @graphql-codegen/cli @graphql-codegen/typescript-apollo-angular 52 | ``` 53 | 54 | ## GraphQL ESLint 55 | 56 | [GraphQL ESLint](https://github.com/B2o5T/graphql-eslint) is a parser, plugin and set rules for 57 | GraphQL (for schema and operations). Easily customizable with custom rules. Integrates with IDEs and 58 | modern GraphQL tools. 59 | 60 | - Integrates with ESLint core (as a ESTree parser). 61 | - Works on `.graphql` files, `gql` or `graphql` usages and `/* GraphQL */` magic comments. 62 | - Lints both GraphQL schema and GraphQL operations. 63 | - Extended type info for more advanced usages 64 | - Supports ESLint directives (for example: `eslint-disable-next-line`) 65 | - Easily extendable - supports custom rules based on GraphQL's AST and ESLint API. 66 | - Validates, lints, prettifies and checks for best practices across GraphQL schema and GraphQL 67 | operations. 68 | - Integrates with [`graphql-config`](https://graphql-config.com) 69 | - Integrates and visualizes lint issues in popular IDEs (VSCode / WebStorm) 70 | 71 | ## Collection of GraphQL Scalars 72 | 73 | [GraphQL Scalars](https://github.com/Urigo/graphql-scalars) is a library of custom GraphQL Scalars 74 | for creating precise type-safe GraphQL schemas. 75 | 76 | ## Generate REST API Out of GraphQL 77 | 78 | [Sofa](https://www.sofa-api.com) takes your GraphQL Schema, looks for available queries, mutations 79 | and subscriptions and turns all of that into REST API. 80 | 81 | ```ts 82 | import express from 'express'; 83 | import sofa from 'sofa-api'; 84 | 85 | const app = express(); 86 | 87 | app.use(sofa({ schema })); 88 | 89 | app.listen(); 90 | 91 | // GET /users 92 | // GET /messages 93 | ``` 94 | 95 | ## Turn Anything into GraphQL API 96 | 97 | [GraphQL Mesh](https://graphql-mesh.com/) allows you to use GraphQL query language to access data in 98 | remote APIs that don't run GraphQL (and also ones that do run GraphQL). 99 | 100 | ## Develop GraphQL API with GraphQL Modules 101 | 102 | [GraphQL Modules](https://graphql-modules.com) lets you separate your backend implementation to 103 | small, reusable, easy-to-implement and easy-to-test pieces. 104 | 105 | ## Other Tools and Libraries 106 | 107 | Packages listed above are specific to Angular, but it's possible to use any Apollo related package 108 | with Apollo Angular. 109 | 110 | - [Apollo Links created by community](https://www.apollographql.com/docs/link/links/community.html) 111 | - [Official Apollo Links](https://apollographql.com/docs/link/#linkslist) 112 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@master 11 | 12 | - uses: actions/setup-node@master 13 | with: 14 | node-version: 22 15 | 16 | - uses: actions/cache@v4 17 | name: Cache node_modules 18 | with: 19 | path: '**/node_modules' 20 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 21 | restore-keys: | 22 | ${{ runner.os }}-yarn- 23 | 24 | - name: Install 25 | run: yarn 26 | 27 | - name: Build 28 | run: yarn build 29 | 30 | - uses: actions/cache@v4 31 | name: Share build 32 | with: 33 | path: 'packages/apollo-angular/build' 34 | key: ${{ github.sha }} 35 | 36 | - name: Pack 37 | run: 38 | (cd packages/apollo-angular/build && yarn pack --filename apollo-angular.tgz && mv 39 | apollo-angular.tgz ../apollo-angular.tgz) 40 | 41 | - uses: actions/cache@v4 42 | name: Share tarball 43 | with: 44 | path: 'packages/apollo-angular/apollo-angular.tgz' 45 | key: ${{ github.sha }} 46 | 47 | - uses: actions/cache@v4 48 | name: Share script 49 | with: 50 | path: 'scripts/prepare-e2e.js' 51 | key: ${{ github.sha }} 52 | 53 | tests: 54 | name: Tests 55 | needs: build 56 | runs-on: ubuntu-latest 57 | 58 | steps: 59 | - uses: actions/checkout@master 60 | 61 | - uses: actions/setup-node@master 62 | with: 63 | node-version: 22 64 | 65 | - uses: actions/cache@v4 66 | name: Cache node_modules 67 | with: 68 | path: '**/node_modules' 69 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 70 | restore-keys: | 71 | ${{ runner.os }}-yarn- 72 | 73 | - name: Install 74 | run: yarn 75 | 76 | - uses: actions/cache@v4 77 | name: Share build 78 | with: 79 | path: 'packages/apollo-angular/build' 80 | key: ${{ github.sha }} 81 | 82 | - name: Test 83 | run: yarn test 84 | 85 | prettier: 86 | runs-on: ubuntu-latest 87 | steps: 88 | - uses: actions/checkout@master 89 | 90 | - uses: actions/setup-node@master 91 | with: 92 | node-version: 22 93 | 94 | - uses: actions/cache@v4 95 | name: Cache node_modules 96 | with: 97 | path: '**/node_modules' 98 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 99 | restore-keys: | 100 | ${{ runner.os }}-yarn- 101 | 102 | - name: Install 103 | run: yarn 104 | 105 | - run: ./node_modules/.bin/prettier --check . 106 | 107 | angular: 108 | name: 109 | Test on Angular ${{ matrix.angular_version }} GraphQL ${{ matrix.graphql_version }} Node ${{ 110 | matrix.node_version }} 111 | needs: build 112 | runs-on: ubuntu-latest 113 | strategy: 114 | matrix: 115 | angular_version: [19, 20, 21] 116 | graphql_version: [16] 117 | node_version: [20, 22, 24] 118 | exclude: 119 | - angular_version: 19 120 | node_version: 24 121 | steps: 122 | - name: Use Node.js ${{ matrix.node_version }} 123 | uses: actions/setup-node@master 124 | with: 125 | node-version: ${{ matrix.node_version }} 126 | 127 | - uses: actions/cache@v4 128 | name: Share tarball 129 | with: 130 | path: 'packages/apollo-angular/apollo-angular.tgz' 131 | key: ${{ github.sha }} 132 | 133 | - uses: actions/cache@v4 134 | name: Share script 135 | with: 136 | path: 'scripts/prepare-e2e.js' 137 | key: ${{ github.sha }} 138 | 139 | - name: Install Angular CLI 140 | run: npm install @angular/cli@${{ matrix.angular_version }} --global 141 | 142 | - name: ng new 143 | run: ng new testapp --package-manager yarn --defaults --minimal --skip-git 144 | 145 | - name: ng add apollo-angular 146 | run: 147 | (cd testapp && ng add ../packages/apollo-angular/apollo-angular.tgz --graphql '${{ 148 | matrix.graphql_version }}.0.0' --defaults --verbose --skip-confirmation) 149 | 150 | - name: ng build 151 | run: (cd testapp && yarn ng run testapp:build:production) 152 | 153 | - name: Setup E2E tests 154 | run: | 155 | sudo apt-get update 156 | sudo apt-get install libgtk2.0-0t64 libgtk-3-0t64 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb 157 | (cd testapp && yarn add -D cypress-fail-on-console-error) 158 | 159 | - name: ng add cypress 160 | run: (cd testapp && ng add @cypress/schematic --defaults --verbose --skip-confirmation) 161 | 162 | - name: Update Angular code 163 | run: ./scripts/prepare-e2e.js testapp ${{ matrix.graphql_version }} 164 | 165 | - name: ng e2e 166 | run: (cd testapp && yarn ng run testapp:cypress-run:production) 167 | -------------------------------------------------------------------------------- /packages/apollo-angular/README.md: -------------------------------------------------------------------------------- 1 | [![Angular](https://user-images.githubusercontent.com/25294569/63955021-b99fca80-ca8c-11e9-9362-1ee8083edd2e.gif)](https://www.apollo-angular.com/) 2 | 3 | # [Apollo Angular](https://www.apollo-angular.com/) [![npm version](https://badge.fury.io/js/apollo-angular.svg)](https://badge.fury.io/js/apollo-angular) 4 | 5 | Apollo Angular allows you to fetch data from your GraphQL server and use it in building complex and 6 | reactive UIs using the Angular framework. Apollo Angular may be used in any context that Angular may 7 | be used. In the browser, in NativeScript, or in Node.js when you want to do server-side rendering. 8 | 9 | Apollo Angular requires _no_ complex build setup to get up and running. As long as you have a 10 | GraphQL server you can get started building out your application with Angular immediately. Apollo 11 | Angular works out of the box with both [Angular CLI](https://cli.angular.io/) 12 | (`ng add apollo-angular`) and [NativeScript](https://www.nativescript.org/) with a single install. 13 | 14 | Apollo Angular is: 15 | 16 | 1. **Incrementally adoptable**, so that you can drop it into an existing JavaScript app and start 17 | using GraphQL for just part of your UI. 18 | 1. **Universally compatible**, so that Apollo works with any build setup, any GraphQL server, and 19 | any GraphQL schema. 20 | 1. **Simple to get started with**, so you can start loading data right away and learn about advanced 21 | features later. 22 | 1. **Inspectable and understandable**, so that you can have great developer tools to understand 23 | exactly what is happening in your app. 24 | 1. **Built for interactive apps**, so your users can make changes and see them reflected in the UI 25 | immediately. 26 | 1. **Small and flexible**, so you don't get stuff you don't need. The core is under 12kb compressed. 27 | 1. **Community driven**, because Apollo is driven by the community and serves a variety of use 28 | cases. Everything is planned and developed in the open. 29 | 30 | Get started today on the app you’ve been dreaming of, and let Apollo Angular take you to the moon! 31 | 32 | ## Installation 33 | 34 | It is simple to install Apollo Angular and related libraries 35 | 36 | ```bash 37 | # installing Apollo Angular in Angular CLI 38 | ng add apollo-angular 39 | 40 | # installing each piece independently 41 | yarn add @apollo/client apollo-angular graphql 42 | ``` 43 | 44 | That’s it! You may now use Apollo Angular in any of your Angular environments. 45 | 46 | For an amazing developer experience you may also install the 47 | [Apollo Client Developer tools for Chrome](https://chrome.google.com/webstore/detail/apollo-client-developer-t/jdkknkkbebbapilgoeccciglkfbmbnfm) 48 | which will give you inspectability into your Apollo Angular data. 49 | 50 | - If you are using Apollo-Client v3, please make sure to use `apollo-angular@v3` 51 | > If you are using Apollo-Client v2, please make sure to use `apollo-angular@v1` (and for Angular 52 | > 10 support, make sure to use `v1.10.0`) 53 | 54 | ## Usage 55 | 56 | Now you may create components that are connected to your GraphQL API. 57 | 58 | Finally, to demonstrate the power of Apollo Angular in building interactive UIs let us connect one 59 | of your components to your GraphQL server using the `Apollo` service: 60 | 61 | ```ts 62 | import { Apollo, gql } from 'apollo-angular'; 63 | import { map, Observable } from 'rxjs'; 64 | import { AsyncPipe } from '@angular/common'; 65 | import { Component, OnInit } from '@angular/core'; 66 | 67 | const GET_DOGS = gql` 68 | { 69 | dogs { 70 | id 71 | breed 72 | } 73 | } 74 | `; 75 | 76 | @Component({ 77 | selector: 'dogs', 78 | template: ` 79 |
    80 | @for (dog of dogs | async; track dog.id) { 81 |
  • 82 | {{ dog.breed }} 83 |
  • 84 | } 85 |
86 | `, 87 | }) 88 | export class DogsComponent implements OnInit { 89 | public dogs!: Observable; 90 | 91 | constructor(private readonly apollo: Apollo) {} 92 | 93 | ngOnInit() { 94 | this.dogs = this.apollo 95 | .watchQuery({ 96 | query: GET_DOGS, 97 | }) 98 | .valueChanges.pipe(map(result => result.data && result.data.dogs)); 99 | } 100 | } 101 | ``` 102 | 103 | To learn more about querying with Apollo Angular be sure to start reading the 104 | [documentation article on queries](https://apollo-angular.com/docs/data/queries/). 105 | 106 | ## Documentation 107 | 108 | All the documentation for Apollo Angular including usage articles and helpful recipes lives on: 109 | [https://www.apollo-angular.com/](https://www.apollo-angular.com/) 110 | 111 | ## Contributing 112 | 113 | [Read the Apollo Contributor Guidelines.](CONTRIBUTING.md) 114 | 115 | This project uses Lerna. 116 | 117 | Bootstrapping: 118 | 119 | ```bash 120 | yarn install 121 | ``` 122 | 123 | Running tests locally: 124 | 125 | ```bash 126 | yarn test 127 | ``` 128 | 129 | Formatting code with prettier: 130 | 131 | ```bash 132 | yarn prettier 133 | ``` 134 | 135 | This project uses TypeScript for static typing. You can get it built into your editor with no 136 | configuration by opening this project in [Visual Studio Code](https://code.visualstudio.com/), an 137 | open source IDE which is available for free on all platforms. 138 | --------------------------------------------------------------------------------