Full Stack
Rachèl Heimbach
Principal Consultant @ OpenValue
https://graphql.org/learn/thinking-in-graphs/
https://www.apollographql.com/docs/apollo-server/
https://www.apollographql.com/
type Product {
id: ID!
title: String!
price: Float!
image: String!
}
Non-Nullable!
type Product {
id: ID!
title: String!
price: Float!
image: String!
rating: Rating!
}
type Rating {
count: Int!
rate: Float!
}
type Product {
id: ID!
title: String!
price: Float!
image: String!
rating: Rating!
}
type Rating {
count: Int!
rate: Float!
}
type Query {
products: [Product!]!
}
{
products {
id
title
price
rating {
rate
}
}
}
{
"products": [
{
"id": "1",
"title": "T-shirt",
"price": 10.99,
"rating": {
"rate": 4.9
}
},
...
{
"id": "2000",
...
}
]
}
Non-Nullable list
Non-Nullable product
type Product {
id: ID!
title: String!
price: Float!
image: String!
rating: Rating!
}
type Rating {
count: Int!
rate: Float!
}
type Query {
products(offset: Int! limit: Int!): [Product!]!
}
{
products(offset: 0, limit: 20) {
id
title
price
rating {
rate
}
}
}
{
"products": [
{
"id": "1",
"title": "T-shirt",
"price": 10.99,
"rating": {
"rate": 4.9
}
},
...
{
"id": "20",
...
}
]
}
type Product {
id: ID!
title: String!
price: Float!
image: String!
rating: Rating!
reviews: (offset: Int! limit: Int!): [Review!]!
}
type Rating {
count: Int!
rate: Float!
}
type Query {
products(offset: Int! limit: Int!): [Product!]!
}
{
products(offset: 0, limit: 20) {
id
title
price
reviews(offset: 0, limit: 1) {
id
title
}
}
}
{
"products": [
{
"id": "1",
"title": "T-shirt",
"price": 10.99,
"reviews": [
{
"id": "123123",
"title": "This is great!"
}
]
},
...
]
}
type Product {
id: ID!
title: String!
price: Float!
image: String!
rating: Rating!
}
type Rating {
count: Int!
rate: Float!
}
input PaginationInput {
offset: Int!
limit: Int!
}
type Query {
products(pagination: PaginationInput!): [Product!]!
}
{
products(pagination: { offset: 0, limit: 20 }) {
id
title
price
}
}
type Product {
id: ID!
title: String!
price: Float!
image: String!
rating: Rating!
}
type Rating {
count: Int!
rate: Float!
}
type Query {
product(id: ID!): Product
}
Nullable
type Product {
id: ID!
title: String!
price: Float!
image: String!
rating: Rating!
}
type Rating {
count: Int!
rate: Float!
}
type NotFound {
id: ID!
reason: String!
}
union ProductResult = Product | NotFound
type Query {
product(id: ID!): ProductResult!
}
{
product(id: "1") {
... on Product {
id
title
}
... on NotFound {
id
reason
}
}
}
https://www.apollographql.com/
A resolver is a function that's responsible for populating the data for a single field in your schema.
{
products(offset: 0, limit: 20) {
id
title
}
}
Query.products()
Product.title()
{
products(offset: 0, limit: 20) {
id
title
reviews(offset: 0, limit: 1) {
id
title
}
}
}
Query.products()
Product.title()
Product.reviews()
Review.title()
Optional
{
products(offset: 0, limit: 20) {
id
title
image
price
reviews(offset: 0, limit: 1) {
id
title
}
}
}
Query.products()
Product.price()
Product.reviews()
REST /products
Review microservice
Price microservice
Query.products()
Product.price()
Product.reviews()
1 req -> 20 products
20 req -> 20 prices
20 req -> 20 reviews
41 requests!
?
Batching
async function batchFunction(keys) {
const results = await db.fetchAllKeys(keys)
return keys.map(key => results[key] || new Error(`No result for ${key}`))
}
const loader = new DataLoader(batchFunction)
Query.products()
Product.price()
Product.reviews()
1 req -> 20 products
1 req -> 20 prices
1 req -> 20 reviews
3 requests!
Review.user()
1 req -> 1-20 users
Request context
Query.products()
Product.price()
Product.reviews()
1 req -> 20 products
1 req -> 20 prices
1 req -> 20 reviews
Review.user()
1 req -> 1-20 users
Request context
Query.products()
0 req -> 20 products
Product.price()
0 req -> 20 prices
3 requests!
import { ApolloServer, gql } from "apollo-server";
const typeDefs = gql``;
const resolvers = {};
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
type Product {
id: ID!
title: String!
price: Float!
}
type Query {
products: [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
products: () => [
{
id: '1',
title: "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops",
...
},
...
]
}
};
...
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
import { ProductsService } from "./services/products.service";
const typeDefs = gql`
...
type Query {
products: [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
products: (_obj, _args, context, _info) =>
context.services.products.get()
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
...
context: () => ({
services: {
products: new ProductsService()
}
})
});
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
type Product {
id: ID!
title: String!
price: Float!
}
type Query {
products(pagination: PaginationParams!): [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
...
},
Product: {
price: () => 100
}
};
...
{
"products": [
{
"id": "1",
"price": 100,
},
{
"id": "2",
"price": 100,
},
{
"id": "3",
"price": 100,
},
...
{
"id": "20",
"price": 100,
}
]
}
5 requests!
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
type Product {
id: ID!
title: String!
price: Float!
}
type Query {
products(pagination: PaginationParams!): [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
products: (_obj, args, context, _info) =>
context.services.products.get(args.pagination)
},
Product: {
price: (product, _args, context, _info) =>
context.dataloaders.price.load(product.id)
}
};
...
2 requests!
type Product {
id: ID!
title: String!
category: String!
description: String!
image: String!
inCart: Boolean!
price: Float!
rating: Rating!
}
---- Product subgraph ----
type Product {
id: ID!
title: String!
category: String!
description: String!
image: String!
}
---- Cart subgraph ----
extend type Product {
inCart: Boolean!
}
---- Price subgraph ----
extend type Product {
price: Float!
}
---- Review subgraph ----
type Rating {
count: Int!
rate: Float!
}
extend type Product {
rating: Rating!
}
Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. Use it to fetch, cache, and modify application data, all while automatically updating your UI.
import { InMemoryCache, ApolloClient, HttpLink } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({ uri: 'http://...' }),
cache: new InMemoryCache(options)
});
import { InMemoryCache, ApolloClient, HttpLink, gql } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({ uri: 'http://...' }),
cache: new InMemoryCache(options)
});
client.query({
query: gql`
query ProductsList($page: Int!, $size: Int!) {
products(pagination: { page: $page, size: $size }) {
results {
id
title
# ...
}
}
}
`,
variables: {
page: 1,
size: 20,
},
});
import { InMemoryCache, ApolloClient, HttpLink, gql } from '@apollo/client';
const client = new ApolloClient({
link: new HttpLink({ uri: 'http://...' }),
cache: new InMemoryCache(options)
});
client.query({
query: gql`
query ProductsList($page: Int!, $size: Int!) {
products(pagination: { page: $page, size: $size }) {
results {
id
title
# ...
}
}
}
`,
variables: {
page: 1,
size: 20,
},
});
client.mutate({
mutation: gql`
mutation AddToCart($id: ID!, $quantity: Int!) {
addToCart(id: $id, quantity: $quantity)
}
`,
variables: {
id: '1',
quantity: 4,
},
});
https://the-guild.dev/graphql/codegen
query Products($pagination: Params!) {
products(pagination: $pagination) {
results {
id
title
price
description
category
image
rating {
count
rate
}
inCart
}
}
}
@Component({
selector: 'ov-smart-products',
styleUrls: ['./smart-products.component.scss'],
template: `
`,
})
export class SmartProductsComponent implements OnInit {
products$ = this.productsGQL
.watch(PAGINATATION)
.valueChanges.pipe(
map(({ data, loading, error }) => ({
loading,
error,
data: data.products?.results,
}))
);
constructor(private productsGQL: ProductsGQL) {}
}
<ng-container *ngIf="products$ | async as products">
<p *ngIf="products.loading; else notLoading">
Loading...
</p>
<ng-template #notLoading>
<ov-products
*ngIf="products.data"
[products]="products.data"></ov-products>
<ov-message
*ngIf="products.error"
[error]="products.error"></ov-message>
</ng-template>
</ng-container>
query Products($pagination: Params!) {
products(pagination: $pagination) {
results {
id
title
price
description
category
image
rating {
count
rate
}
inCart
}
}
}
export const SmartProductsComponent = () => {
const { loading, data, error } = useProductsQuery({
variables: {
pagination: {
page: 1,
size: 6,
},
},
});
const products = productsQuery?.products.results;
if(loading) {
return <p>Loading...</p>;
}
if(error) {
return <Message error={error}></Message>;
}
if(products) {
return <Products products={products}></Products>;
}
return <p>No data?<p>;
}
Error
query Products($pagination: Params!) {
products(pagination: $pagination) {
results {
id
title
price
description
category
image
rating {
count
rate
}
inCart
}
}
}
export const SmartProductsComponent = () => {
const { loading, data, error } = useProductsQuery({
fetchPolicy: 'cache-first',
variables: {
pagination: {
page: 1,
size: 6,
},
},
});
const products = productsQuery?.products.results;
if(loading) {
return <p>Loading...</p>;
}
if(error) {
return <Message error={error}></Message>;
}
if(products) {
return <Products products={products}></Products>;
}
return <p>No data?<p>;
}
cache-first (default)
Data
Data
cache-only
Data
Error
cache-and-network
Data
Data
network-only
Data
no-cache
Data
refetchQueries
Ok
Mutate
Data
1 + n requests
update
Mutate
Update
1 request
Ok
Mutate
Update
Ok
update + optimistic response
https://principledgraphql.com/
https://github.com/Rachnerd/webshop
Branch: graphql-assignment-1
Setup + basic query
UI
UI
UI
UI
Smart
Smart
Page
Template
Apollo
You can use Apollo Server as:
overwrite: true
schema: "http://localhost:4000"
documents: null
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-resolvers"
...
export type Resolvers<ContextType = Context> = {
Product?: ProductResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
};
export type ProductResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Product'] = ResolversParentTypes['Product']> = {
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
price?: Resolver<ResolversTypes['Float'], ParentType, ContextType>;
title?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type QueryResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = {
products?: Resolver<Array<ResolversTypes['Product']>, ParentType, ContextType>;
};
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
};
export type Product = {
__typename?: 'Product';
id: Scalars['ID'];
price: Scalars['Float'];
title: Scalars['String'];
};
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
type Product {
id: ID!
title: String!
price: Float!
}
type Query {
products: [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
products: () => [
{
id: '1',
title: "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops"
},
...
]
}
};
...
Code generation
import fetch from "node-fetch";
import { Products } from "../generated/graphql";
export class ProductsService {
async get(): Promise<Product[]> {
const res = await fetch(
`http://localhost:8080/products`
);
const { results } =
await res.json();
return results;
}
}
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
import { ProductsService } from "./services/products.service";
const typeDefs = gql`
...
`;
const resolvers: Resolvers = {
Query: {
products: () => new ProductsService().get()
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded"
});
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
import { ProductsService } from "./services/products.service";
const typeDefs = gql`
...
`;
const resolvers: Resolvers = {
Query: {
products: (_obj, _args, context, _info) => ...
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
context: () => ({}),
});
overwrite: true
schema: "http://localhost:4000"
documents: null
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-resolvers"
config:
contextType: ../context#Context
export interface Context {}
overwrite: true
schema: "http://localhost:4000"
documents: null
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-resolvers"
config:
contextType: ../context#Context
import { ProductsService } from "./services/products.service";
export interface Context {
services: {
products: ProductsService;
};
}
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
import { ProductsService } from "./services/products.service";
const typeDefs = gql`
...
`;
const resolvers: Resolvers = {
Query: {
products: (_obj, _args, context, _info) =>
context.services.products.get()
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
context: () => ({
services: {
products: new ProductsService()
}
})
});
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
import { ProductsService } from "./services/products.service";
const typeDefs = gql`
...
`;
const resolvers: Resolvers = {
Query: {
products: (_obj, _args, { services: { products }}, _info) =>
products.get()
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
context: () => ({
services: {
products: new ProductsService()
}
})
});
...
const typeDefs = gql`
type Product {
id: ID!
title: String!
price: Float!
}
input PaginationParams {
page: Int!
size: Int!
}
type Query {
products(pagination: PaginationParams!): [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
products: (_obj, args, { services: { products }}, _info) =>
products.get(args.pagination)
}
};
...
...
const typeDefs = gql`
type Product {
id: ID!
title: String!
price: Float!
}
input PaginationParams {
page: Int!
size: Int!
}
type Query {
products(pagination: PaginationParams!): [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
products: (_obj, { pagination }, { services: { products }}, _info) =>
products.get(pagination)
}
};
...
Contex, pagination
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
type Product {
id: ID!
title: String!
price: Float!
}
type Query {
products(pagination: PaginationParams!): [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
...
},
Product: {
price: () => 100
}
};
...
{
"products": [
{
"id": "1",
"price": 100,
},
{
"id": "2",
"price": 100,
},
{
"id": "3",
"price": 100,
},
...
{
"id": "20",
"price": 100,
}
]
}
overwrite: true
schema: "http://localhost:4000"
documents: null
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-resolvers"
config:
contextType: ../context#Context
import { ProductsService } from "./services/products.service";
export interface Context {
services: {
products: ProductsService;
};
}
overwrite: true
schema: "http://localhost:4000"
documents: null
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-resolvers"
config:
contextType: ../context#Context
import { ProductsService } from "./services/products.service";
import { PriceService } from "./services/price.service";
export interface Context {
services: {
products: ProductsService;
prices: PricesService;
};
}
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
type Product {
id: ID!
title: String!
price: Float!
}
type Query {
products(pagination: PaginationParams!): [Product!]!
}
`;
const resolvers: Resolvers = {
Query: {
...
},
Product: {
price: (product, _args, { services: { prices }}, _info) =>
prices.getById(product.id);
}
};
...
Price resolver
input 1
input 2
input 3
request
response
output 2
output 3
output 1
overwrite: true
schema: "http://localhost:4000"
documents: null
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-resolvers"
config:
contextType: ../context#Context
import { ProductsService } from "./services/products.service";
import { PriceService } from "./services/price.service";
export interface Context {
services: {
products: ProductsService;
prices: PricesService;
};
dataloaders: {
price: DataLoader<string, number>;
};
}
import fetch from "node-fetch";
import { Products } from "../generated/graphql";
export class PricesService {
async getById(id: string): Promise<number> {
const res = await fetch(
`http://localhost:8080/price/${id}`
);
return await res.json();
}
async getByIds(ids: readonly string[]): Promise<number[]> {
const res = await fetch(
`http://localhost:8080/price?ids=${ids.join(",")}`
);
return await res.json();
}
}
...
import DataLoader from "dataloader";
...
const resolvers: Resolvers = {
...
Product: {
price: (product, _args, { dataloaders: { prices } }) =>
prices.load(product.id),
}
};
const server = new ApolloServer({
...
context: () => {
const pricesService = new PricesService();
return {
services: {
products: new ProductsService(),
prices: pricesService,
},
dataloaders: {
prices: new DataLoader(pricesService.getByIds),
}
};
}
});
price, products, cartInfo, cart dataloaders
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
interface Product {
id: ID!
title: String!
price: Float!
}
type ProductInStock implements Product {
id: ID!
title: String!
price: Float!
quantity: Quantity!
}
type Quantity {
min: Int!
step: Int!
max: Int!
}
type ProductOutOfStock implements Product {
id: ID!
title: String!
price: Float!
}
type ProductReplaced implements Product {
id: ID!
title: String!
price: Float!
replacement: ProductInStock!
}
type Query {
products(pagination: PaginationParams!): [Product!]!
}
`;
const resolvers: Resolvers = {
...
};
...
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
...
`;
const resolvers: Resolvers = {
Product: {
__resolveType: (product: Product) => {
if (!!(product as ProductInStock).quantity) {
return "ProductInStock";
}
if (!!(product as ProductReplaced).replacement) {
return "ProductReplaced";
}
return "ProductOutOfStock";
},
},
};
...
import { ApolloServer, gql } from "apollo-server";
import { Resolvers } from "./generated/graphql";
const typeDefs = gql`
...
`;
const resolvers: Resolvers = {
ProductInStock: {
__isTypeOf: (product) => !!product.quantity,
},
ProductReplaced: {
__isTypeOf: (product) => !!product.replacement,
},
ProductOutOfStock: {
__isTypeOf: () => true,
},
};
...
This only works if it is defined after the other types!
Product interface
Product interface
const resolvers: Resolvers = {
Product: {
__resolveType: (product) => {
if (!!(product as ProductInStock).quantity) {
return "ProductInStock";
}
if (!!(product as ProductReplaced).replacement) {
return "ProductReplaced";
}
return "ProductOutOfStock";
},
},
}
// vs
const resolvers: Resolvers = {
ProductInStock: {
__isTypeOf: (product) => !!product.quantity,
},
ProductReplaced: {
__isTypeOf: (product) => !!product.replacement,
},
ProductOutOfStock: {
__isTypeOf: (product) => !!product.title,
},
}
// Do
const resolvers: Resolvers = {
ProductInStock: {
price: ({ id }, _args, { dataloaders: { price }})
=> price.load(id)
},
}
// Don't
const resolvers: Resolvers = {
ProductInStock: {
price: ({ id }, _args, { services: { price }})
=> price.getById(id)
},
}
const server = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
cache: "bounded",
context: (): Context => {
const priceService = new PriceService();
const cartService = new CartService();
const productsService = new ProductsService();
return {
services: {
products: productsService,
price: priceService,
cart: cartService,
},
dataloaders: {
products: new DataLoader(...),
price: new DataLoader(...),
cart: new DataLoader(...),
cartInfo: new DataLoader(...),
},
};
},
});
Server
Apollo Client stores the results of your GraphQL queries in a local, normalized, in-memory cache. This enables Apollo Client to respond almost immediately to queries for already-cached data, without even sending a network request.
You can pass a
possibleTypes
option to theInMemoryCache
constructor to specify supertype-subtype relationships in your schema. This object maps the name of an interface or union type (the supertype) to the types that implement or belong to it (the subtypes).
overwrite: true
schema: "http://localhost:4000"
documents: "src/**/*.graphql"
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-operations"
- "typescript-apollo-angular"
./schema.graphql:
- "schema-ast"
./fragmentTypes.json:
plugins:
- fragment-matcher
config:
addExplicitOverride: true
nonOptionalTypename: true
import introspectionQueryResultData from '../../fragmentTypes.json';
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
return {
link: httpLink.create({ uri }),
cache: new InMemoryCache({
possibleTypes: introspectionQueryResultData.possibleTypes,
}),
};
}
{
"possibleTypes": {
"Product": [
"ProductInStock",
"ProductOutOfStock",
"ProductReplaced"
],
"ProductResult": [
"NotFound",
"ProductInStock",
"ProductOutOfStock",
"ProductReplaced"
]
}
}
Performance issue
In addition to fetching data using queries, Apollo also handles GraphQL mutations. Mutations are identical to queries in syntax, the only difference being that you use the keyword
mutation
instead ofquery
to indicate that the operation is used to change the dataset behind the schema.
Add to cart / remove from cart
Fix stale data
It's often possible to predict the most likely result of a mutation before your GraphQL server returns it. Apollo Client can use this "most likely result" to update your UI optimistically, making your app feel more responsive to the user.
Optimistic response
Enable batching + optimize
export class SmartComponent implements OnInit {
data$ = this.completeDataSetGQL
.watch({ ... }, {
fetchPolicy: 'cache-only',
})
.valueChanges.pipe(
map(...)
);
ngOnInit(): void {
this.fastFieldsGQL
.fetch({ ... })
.subscribe(() => console.log('Fast is loaded'));
this.slowFieldsGQL
.fetch({ ... })
.subscribe(() => console.log('Slow is loaded'));
}
}
const uri = 'http://localhost:4000';
export function createApollo(httpLink: HttpLink)
: ApolloClientOptions<any> {
return {
link: split(
({ getContext }) => getContext()['noBatch'],
httpLink.create({ uri }),
new BatchHttpLink({ uri })
),
...
};
}
// ...
this.productsListPricesGql
.fetch(PAGINATATION, {
context: {
noBatch: true,
},
})
.subscribe(() => console.log('Data is loaded'));
export class SmartComponent implements OnInit {
addProductToCart({ product: { id, price }, quantity }: AddToCartEvent) {
this.addToCartGQL
.mutate(
{ id, quantity },
{
optimisticResponse: {
__typename: 'Mutation',
addToCart: true,
},
update: (proxy, _, { variables }) => {
const { id, quantity } = variables!;
proxy.writeFragment({
id: `ProductInStock:${id}`,
fragment: CartInfoFragmentDoc,
data: {
__typename: 'ProductInStock',
cartInfo: {
id,
quantity,
total: price! * quantity,
},
},
});
},
}
)
.subscribe(() => console.log('Product is added to cart'));
}
}
export class SmartComponent implements OnInit {
data$ = this.completeDataSetGQL
.watch({ ... }, {
fetchPolicy: 'cache-only',
})
.valueChanges.pipe(
map(...)
);
ngOnInit(): void {
this.fastFieldsGQL
.fetch({ ... })
.subscribe(() => console.log('Fast is loaded'));
this.slowFieldsGQL
.fetch({
fetchPolicy: 'cache-and-network',
})
.subscribe(() => console.log('Slow is loaded'));
}
}
Client
overwrite: true
schema: "http://localhost:4000"
documents: "src/**/*.graphql"
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-operations"
- "typescript-apollo-angular"
./schema.graphql:
- "schema-ast"
config:
addExplicitOverride: true
nonOptionalTypename: true
query ProductsList($page: Int!, $size: Int!) {
products(pagination: { page: $page, size: $size }) {
results {
id
title
description
... on ProductInStock {
quantity {
min
step
max
}
}
... on ProductReplaced {
replacement {
id
title
}
}
}
}
}
overwrite: true
schema: "http://localhost:4000"
documents: "src/**/*.graphql"
generates:
src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-operations"
- "typescript-apollo-angular"
./schema.graphql:
- "schema-ast"
config:
addExplicitOverride: true
nonOptionalTypename: true
export type ProductsListQuery = {
__typename: 'Query',
products: {
__typename: 'Products',
results: Array<{
__typename: 'ProductInStock',
id: string,
title: string,
description: string,
quantity: {
__typename: 'Quantity',
min: number,
step: number,
max: number
}
} | {
__typename: 'ProductOutOfStock',
price: number,
id: string,
...
}
}
@Injectable({
providedIn: 'root'
})
export class ProductsListGQL extends Apollo.Query<ProductsListQuery, ProductsListQueryVariables> {
override document = ProductsListDocument;
constructor(apollo: Apollo.Apollo) {
super(apollo);
}
}
export function useProductsListQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<ProductsListQuery, ProductsListQueryVariables>) {
return ApolloReactHooks.useQuery<ProductsListQuer, ProductsListQueryVariables>(ProductsListDocument, baseOptions);
}