├── .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 |
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 |
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 | 
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 | [](https://www.apollo-angular.com/)
2 |
3 | # [Apollo Angular](https://www.apollo-angular.com/) [](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 |
--------------------------------------------------------------------------------