Angular 119 🅰️ Angular and GraphQL
GraphQL is a query language for APIs and a server-side runtime that lets clients request exactly the data they need, and nothing more. Where REST exposes multiple endpoints that return fixed data shapes, GraphQL exposes a single endpoint and a strongly-typed schema, and the client specifies which fields it wants in the query itself. The server resolves those fields against whatever data sources it uses — databases, microservices, or other APIs — and returns a JSON response that mirrors the query’s shape.
This chapter covers how GraphQL differs from REST, the schema and type system, queries, mutations, and subscriptions, the Apollo Angular client setup, using watchQuery for reactive data, mutations and cache updates, code generation for type safety, and the patterns for structuring GraphQL operations in Angular services.
Key point: GraphQL uses a single endpoint and a typed schema. Clients send queries specifying exact fields; the server resolves them. In Angular, Apollo Angular provides a watchQuery method that returns an observable which updates when the cache changes, and mutate for writes. Code generation with @graphql-codegen/typescript-apollo-angular produces typed services that replace manual interfaces.
Why GraphQL exists alongside REST
The over-fetching problem. A REST endpoint returns a fixed representation. If the UI needs only a product’s title and price, a REST endpoint that returns the full product object — description, images, inventory, categories — sends data the client does not use. GraphQL lets the client specify exactly which fields to return, eliminating over-fetching.
The under-fetching problem. A UI that needs a user and their posts may require two REST calls: one to /users/:id and one to /users/:id/posts. GraphQL allows a single query that traverses the relationship between User and Post, returning both in one request.
The versioning problem. REST APIs evolve by adding new endpoints or versioning the API (/v2/). GraphQL schemas evolve by adding fields; existing queries continue to work, and deprecated fields can be removed once usage declines. There is no need for parallel versions of the API.
The tooling problem. GraphQL schemas are introspectable. Tools can query the schema to discover available types and fields, enabling autocompletion, validation, and automatic documentation. This makes the API self-documenting in a way REST is not.
The client-specified response problem. In REST, the server decides what data a resource returns. In GraphQL, the client decides. This is a fundamental shift: the API describes capabilities, and the client selects what it needs.
a. The schema and type system
A GraphQL service is defined by a schema written in Schema Definition Language (SDL). The schema declares the types that exist, their fields, and the operations that can be performed.
type Post {
id: ID!
title: String!
content: String
author: Author!
createdAt: String!
}
type Author {
id: ID!
name: String!
email: String
posts: [Post!]!
}
type Query {
posts: [Post!]!
post(id: ID!): Post
authors: [Author!]!
}
type Mutation {
createPost(title: String!, content: String!): Post!
deletePost(id: ID!): Boolean!
}
The exclamation mark marks a field as non-nullable. The Query type defines read operations; the Mutation type defines write operations. A Subscription type defines real-time operations where the server pushes updates to the client.
The schema is the contract between client and server. Clients can introspect it to discover available operations, and tools use it to validate queries before they are sent.
b. Queries, mutations, and variables
A GraphQL query mirrors the shape of the response. The client specifies the fields it wants, and the server returns only those fields.
query GetPosts {
posts {
id
title
author {
name
}
}
}
The response contains exactly the requested fields:
{
"data": {
"posts": [
{
"id": "1",
"title": "Hello GraphQL",
"author": { "name": "Alice" }
}
]
}
}
Variables separate the query structure from the input values, making queries reusable and validated independently of the data:
query GetPostsOfAuthor($authorId: Int!) {
postsOf(authorId: $authorId) {
id
title
}
}
The variable $authorId is declared with its type (Int!) and passed as a separate JSON object when the query is executed.
Mutations use the same syntax but are write operations:
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
}
}
Mutations return data just like queries, and the client specifies which fields of the created or modified object it wants back.
c. Setting up Apollo Angular
Apollo Angular is the GraphQL client for Angular. It provides a service that executes queries and mutations, an in-memory cache that normalizes data by type and ID, and observables that update when the cache changes.
Installation uses the Angular CLI schematic:
ng add apollo-angular
This installs the required packages and configures the client. The GraphQL endpoint is set in the generated provider:
import { ApplicationConfig } from '@angular/core';
import { provideApollo } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { InMemoryCache } from '@apollo/client/core';
import { inject } from '@angular/core';
export const graphqlProvider = provideApollo(() => {
const httpLink = inject(HttpLink);
return {
link: httpLink.create({ uri: 'https://api.example.com/graphql' }),
cache: new InMemoryCache(),
};
});
The InMemoryCache caches query results by type and ID. When a mutation updates an object, Apollo can automatically update the cached object and notify all components watching it, so the UI stays in sync without manual refetching.
d. Querying with watchQuery
The Apollo service provides watchQuery for queries that should update when the cache changes and query for one-time fetches. watchQuery returns a QueryRef whose valueChanges property is an RxJS observable.
import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { Apollo, gql } from 'apollo-angular';
import { map } from 'rxjs';
const GET_POSTS = gql`
query GetPosts {
posts {
id
title
author { name }
}
}
`;
@Component({
selector: 'app-posts',
imports: [AsyncPipe],
template: `
@if (posts$ | async; as posts) {
<ul>
@for (post of posts; track post.id) {
<li>{{ post.title }} by {{ post.author.name }}</li>
}
</ul>
}
`,
})
export class PostsComponent {
private readonly apollo = inject(Apollo);
posts$ = this.apollo.watchQuery<{ posts: Post[] }>({
query: GET_POSTS,
}).valueChanges.pipe(
map(result => result.data.posts),
);
}
The watchQuery observable emits once when the query completes and again whenever the cache entry for posts changes. The AsyncPipe subscribes and unsubscribes automatically, and @if with as avoids multiple async pipe subscriptions.
A key advantage of watchQuery over query is cache reactivity. If a mutation adds a post to the cache, any component watching posts receives the updated list without refetching.
e. Mutations and cache updates
Mutations are executed with the mutate method. After a mutation, the cache must be updated to reflect the change. Apollo provides two strategies: refetchQueries to re-execute queries after the mutation, and manual cache updates via update.
const CREATE_POST = gql`
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
}
}
`;
createPost(title: string, content: string) {
return this.apollo.mutate({
mutation: CREATE_POST,
variables: { title, content },
refetchQueries: [{ query: GET_POSTS }],
});
}
The refetchQueries option tells Apollo to re-execute the specified queries after the mutation completes, ensuring the UI reflects the new data. For more precise control, the update function receives the cache and the mutation result, and can write the new object directly into the cache.
Apollo’s normalized cache means that if the mutation returns the created post and the query for posts includes the same fields, the cache can automatically merge the new post into the existing list, and watchQuery emits the updated data.
f. Code generation for type safety
Manually maintaining TypeScript interfaces that match GraphQL queries is error-prone. GraphQL Code Generator with the @graphql-codegen/typescript-apollo-angular plugin generates typed Apollo services for each operation, eliminating manual interfaces and ensuring that queries and types stay in sync with the schema.
Installation:
npm i graphql
npm i -D @graphql-codegen/cli
npm i -D @graphql-codegen/typescript
npm i -D @graphql-codegen/typescript-operations
npm i -D @graphql-codegen/typescript-apollo-angular
Configuration in codegen.ts:
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'http://localhost:4000/graphql',
documents: './src/**/*.ts',
generates: {
'./src/graphql/generated.ts': {
plugins: [
'typescript',
'typescript-operations',
'typescript-apollo-angular',
],
},
},
};
export default config;
Running npm run generate produces a generated.ts file with typed services. The component then uses the generated service directly:
import { PostsGQL, PostsQuery } from './graphql/generated';
@Component({ /* ... */ })
export class PostsComponent {
posts$: Observable<PostsQuery['posts']>;
constructor(postsGQL: PostsGQL) {
this.posts$ = postsGQL.watch().valueChanges.pipe(
map(result => result.data.posts),
);
}
}
The generated PostsGQL service provides a watch() method that returns a QueryRef with fully typed data. The type PostsQuery['posts'] is derived from the query definition, so if the query changes, the type changes automatically.
Complete Example Session
// ============================================
// PART 1: INSTALL APOLLO ANGULAR
// ============================================
// ng add apollo-angular
// ============================================
// PART 2: CONFIGURE THE CLIENT
// ============================================
import { ApplicationConfig } from '@angular/core';
import { provideApollo } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { InMemoryCache } from '@apollo/client/core';
import { inject } from '@angular/core';
export const graphqlProvider = provideApollo(() => {
const httpLink = inject(HttpLink);
return {
link: httpLink.create({ uri: 'http://localhost:4000/graphql' }),
cache: new InMemoryCache(),
};
});
// ============================================
// PART 3: DEFINE A QUERY WITH gql
// ============================================
import { gql } from 'apollo-angular';
const GET_POSTS = gql`
query GetPosts {
posts {
id
title
author {
name
}
}
}
`;
// ============================================
// PART 4: FETCH WITH watchQuery
// ============================================
import { Component, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { Apollo } from 'apollo-angular';
import { map } from 'rxjs';
@Component({
selector: 'app-posts',
imports: [AsyncPipe],
template: `
@if (posts$ | async; as posts) {
<ul>
@for (post of posts; track post.id) {
<li>{{ post.title }} by {{ post.author.name }}</li>
}
</ul>
}
`,
})
export class PostsComponent {
private readonly apollo = inject(Apollo);
posts$ = this.apollo.watchQuery<{ posts: Post[] }>({
query: GET_POSTS,
}).valueChanges.pipe(
map(result => result.data.posts),
);
}
// ============================================
// PART 5: DEFINE A MUTATION
// ============================================
const CREATE_POST = gql`
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
}
}
`;
// ============================================
// PART 6: EXECUTE A MUTATION
// ============================================
createPost(title: string, content: string) {
return this.apollo.mutate({
mutation: CREATE_POST,
variables: { title, content },
refetchQueries: [{ query: GET_POSTS }],
});
}
// ============================================
// PART 7: USING VARIABLES
// ============================================
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
getUser(id: string) {
return this.apollo.watchQuery({
query: GET_USER,
variables: { id },
}).valueChanges;
}
// ============================================
// PART 8: CODE GENERATION CONFIGURATION
// ============================================
// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'http://localhost:4000/graphql',
documents: './src/**/*.ts',
generates: {
'./src/graphql/generated.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-apollo-angular'],
},
},
};
export default config;
// ============================================
// PART 9: USING GENERATED SERVICES
// ============================================
import { PostsGQL } from './graphql/generated';
@Component({ /* ... */ })
export class PostsComponent {
posts$: Observable<PostsQuery['posts']>;
constructor(postsGQL: PostsGQL) {
this.posts$ = postsGQL.watch().valueChanges.pipe(
map(result => result.data.posts),
);
}
}
// ============================================
// PART 10: CACHE UPDATE AFTER MUTATION
// ============================================
this.apollo.mutate({
mutation: CREATE_POST,
variables: { title, content },
update: (cache, { data }) => {
const newPost = data?.createPost;
if (!newPost) return;
const existing = cache.readQuery<{ posts: Post[] }>({ query: GET_POSTS });
if (existing) {
cache.writeQuery({
query: GET_POSTS,
data: { posts: [...existing.posts, newPost] },
});
}
},
});
These ten parts cover installation, client configuration, query definition, watchQuery usage, mutation definition, mutation execution, variables, code generation configuration, using generated services, and manual cache updates.
Quick Reference
GraphQL vs REST
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple | Single |
| Response shape | Server-defined | Client-specified |
| Over-fetching | Common | Eliminated |
| Under-fetching | Requires multiple calls | Single query |
| Versioning | URL versions | Schema evolution |
| Tooling | Limited introspection | Full introspection |
Apollo Angular API
| Method | Purpose | Returns |
|---|---|---|
watchQuery | Reactive query | QueryRef with valueChanges |
query | One-time query | Observable |
mutate | Write operation | Observable |
subscribe | Real-time subscription | Observable |
gql Tag
| Usage | Example |
|---|---|
| Query | gql\query GetPosts { posts { id } }“ |
| Mutation | gql\mutation CreatePost($t: String!) { … }“ |
| Fragment | gql\fragment PostFields on Post { id title }“ |
Code Generation
| Package | Purpose |
|---|---|
@graphql-codegen/cli | CLI |
@graphql-codegen/typescript | Base types |
@graphql-codegen/typescript-operations | Operation types |
@graphql-codegen/typescript-apollo-angular | Typed Angular services |
Best Practices
✅ Do This:
// Use watchQuery for reactive data
posts$ = this.apollo.watchQuery({ query: GET_POSTS }).valueChanges;
// Use variables for reusable queries
const GET_USER = gql`query GetUser($id: ID!) { user(id: $id) { name } }`;
// Generate types from the schema
npm run generate
// Update cache after mutations
refetchQueries: [{ query: GET_POSTS }]
❌ Don’t Do This:
// Don't use query() when data should be reactive
this.apollo.query({ query: GET_POSTS }); // ❌ one-time only
// Don't hardcode values in queries
gql`{ user(id: "123") { name } }` // ❌ use variables
// Don't manually maintain types
interface Post { id: string; title: string; } // ❌ use codegen
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Stale data after mutation | Cache not updated | Use refetchQueries or update |
| Types out of sync | Manual interfaces | Use GraphQL Code Generator |
| Query not reactive | Used query instead of watchQuery | Use watchQuery |
| Variables not passed | Missing variables option | Pass variables in the options object |
| Multiple async pipes | Multiple subscriptions | Use @if (x$ | async; as x) |
| Subscription leak | Not unsubscribing | Use AsyncPipe or takeUntilDestroyed |
Real-World Examples
1. Basic Query
const GET_POSTS = gql`{ posts { id title } }`;
2. Query with Variables
const GET_USER = gql`query GetUser($id: ID!) { user(id: $id) { name } }`;
3. Mutation
const CREATE_POST = gql`
mutation CreatePost($title: String!) {
createPost(title: $title) { id title }
}
`;
4. Reactive Query in Component
posts$ = this.apollo.watchQuery({ query: GET_POSTS }).valueChanges;
5. Mutation with Refetch
this.apollo.mutate({
mutation: CREATE_POST,
refetchQueries: [{ query: GET_POSTS }],
});
6. Manual Cache Update
update: (cache, { data }) => {
cache.writeQuery({ query: GET_POSTS, data: { posts: [...] } });
}
7. Generated Service
import { PostsGQL } from './graphql/generated';
constructor(postsGQL: PostsGQL) { ... }
8. Fragment
const POST_FIELDS = gql`
fragment PostFields on Post { id title author { name } }
`;
9. Error Handling
this.apollo.watchQuery({ query: GET_POSTS }).valueChanges.subscribe({
next: ({ data }) => { ... },
error: (err) => { ... },
});
10. Subscription
this.apollo.subscribe({ query: POST_ADDED }).subscribe({
next: ({ data }) => { ... },
});
Visual
GraphQL vs REST Request Flow
┌──────────────────────────────────────────────────────────────┐
│ REST GraphQL │
│ │
│ GET /users/1 ──▶ { id, name, email, ... } │
│ GET /users/1/posts ──▶ [{ id, title }, ...] │
│ │
│ Two requests. One request. │
│ Over-fetching. Exact fields. │
│ Under-fetching. Related data. │
└──────────────────────────────────────────────────────────────┘
Apollo Cache Reactivity
┌──────────────────────────────────────────────────────────────┐
│ MUTATION CACHE UI │
│ ┌─────────────┐ ┌─────────┐ ┌─────────┐ │
│ │ createPost │────────────▶│ posts │────────▶│ watchQuery│ │
│ └─────────────┘ │ [new] │ │ emits │ │
│ └─────────┘ └─────────┘ │
│ │
│ Mutation writes to cache; watchQuery subscribers receive │
│ the updated data without refetching. │
└──────────────────────────────────────────────────────────────┘
Code Generation Flow
┌──────────────────────────────────────────────────────────────┐
│ schema.graphql queries.ts generated.ts │
│ │ │ │ │
│ └───────────┬───────────┘ │ │
│ ▼ │ │
│ graphql-codegen │ │
│ │ │ │
│ ▼ │ │
│ generated.ts ─────────────────────────────▶│ │
│ │
│ Typed services derived from schema and queries. │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| GraphQL | Query language for APIs with a typed schema |
| Single endpoint | All operations sent to one URL |
| Over-fetching | Eliminated; client specifies fields |
| Under-fetching | Eliminated; single query for related data |
| Schema | Type system defining available operations |
| Query | Read operation |
| Mutation | Write operation |
| Subscription | Real-time push operation |
| Apollo Angular | GraphQL client for Angular |
watchQuery | Reactive query returning observable |
| Cache | Normalized by type and ID |
| Code generation | @graphql-codegen/typescript-apollo-angular |
Key takeaways:
- GraphQL is a query language and runtime, not a database technology. It sits in front of existing data sources—databases, microservices, REST APIs—and provides a unified, typed interface for clients.
- The client specifies the response shape. Unlike REST, where the server decides what a resource returns, GraphQL queries declare exactly which fields the client needs, eliminating over-fetching and under-fetching.
- The schema is the contract. Written in SDL, the schema defines types, queries, mutations, and subscriptions. It is introspectable, enabling tooling and self-documentation.
- Apollo Angular provides
watchQueryfor reactive data. ThevalueChangesobservable emits when the query completes and whenever the cache entry changes, keeping the UI in sync automatically. - Mutations return data like queries. The client specifies which fields of the modified object it wants back, and can use
refetchQueriesorupdateto keep the cache consistent. - Code generation eliminates manual types. GraphQL Code Generator with the Apollo Angular plugin produces typed services from the schema and query documents, ensuring that types stay synchronized with the API.
- Apollo’s normalized cache is the reactivity mechanism. Objects are cached by
__typename:id, so a mutation that updates an object notifies every component watching it, without manual refetching.
Remember: GraphQL in Angular is not a replacement for REST in every case; it is a different contract between client and server. The server publishes a schema describing what can be queried, and the client sends a query specifying exactly what it needs. Apollo Angular implements this contract with an observable-based API and a normalized cache that makes data reactive. The watchQuery method is the core primitive: it executes a query and returns an observable that updates when the cache changes. Mutations write to the cache, and code generation keeps types aligned with the schema. The result is an application where data fetching is declarative, types are generated rather than manually maintained, and the UI stays synchronized with the server state without explicit refetching logic.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!