How to use GraphQL in Angular
GraphQL provides efficient data fetching with client-specified queries, reducing over-fetching and enabling flexible API interactions in Angular applications. As the creator of CoreUI, a widely used open-source UI library, I’ve implemented GraphQL APIs in Angular projects throughout my 12 years of frontend development since 2014. The most straightforward approach is using Angular’s HttpClient to send GraphQL queries as POST requests with query strings and variables. This method enables precise data requirements, type-safe responses with TypeScript interfaces, and reduced network payload compared to REST APIs.
Send GraphQL queries using HttpClient with POST requests containing query strings and variables for flexible data fetching.
import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
interface User {
id: string
name: string
email: string
posts: Post[]
}
interface Post {
id: string
title: string
content: string
createdAt: string
}
interface GraphQLResponse<T> {
data: T
errors?: Array<{ message: string }>
}
@Injectable({
providedIn: 'root'
})
export class GraphQLService {
private apiUrl = 'https://api.example.com/graphql'
constructor(private http: HttpClient) {}
private query<T>(query: string, variables: any = {}): Observable<T> {
const headers = new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.getToken()}`
})
return this.http.post<GraphQLResponse<T>>(
this.apiUrl,
{ query, variables },
{ headers }
).pipe(
map(response => {
if (response.errors) {
throw new Error(response.errors[0].message)
}
return response.data
})
)
}
private getToken(): string {
return localStorage.getItem('authToken') || ''
}
// Query: Fetch single user with posts
getUser(id: string): Observable<{ user: User }> {
const query = `
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
content
createdAt
}
}
}
`
return this.query(query, { id })
}
// Query: Fetch multiple users with pagination
getUsers(limit: number = 10, offset: number = 0): Observable<{ users: User[] }> {
const query = `
query GetUsers($limit: Int!, $offset: Int!) {
users(limit: $limit, offset: $offset) {
id
name
email
}
}
`
return this.query(query, { limit, offset })
}
// Mutation: Create new post
createPost(title: string, content: string): Observable<{ createPost: Post }> {
const mutation = `
mutation CreatePost($title: String!, $content: String!) {
createPost(title: $title, content: $content) {
id
title
content
createdAt
}
}
`
return this.query(mutation, { title, content })
}
// Mutation: Update user profile
updateUser(id: string, name: string, email: string): Observable<{ updateUser: User }> {
const mutation = `
mutation UpdateUser($id: ID!, $name: String!, $email: String!) {
updateUser(id: $id, name: $name, email: $email) {
id
name
email
}
}
`
return this.query(mutation, { id, name, email })
}
// Mutation: Delete post
deletePost(id: string): Observable<{ deletePost: boolean }> {
const mutation = `
mutation DeletePost($id: ID!) {
deletePost(id: $id)
}
`
return this.query(mutation, { id })
}
}
Using GraphQL service in components:
import { Component, OnInit } from '@angular/core'
import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { GraphQLService } from './graphql.service'
@Component({
selector: 'app-user-profile',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div class='profile-container'>
<div *ngIf='loading' class='loading'>
Loading user data...
</div>
<div *ngIf='error' class='error'>
{{ error }}
</div>
<div *ngIf='user && !loading' class='profile'>
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<div class='posts'>
<h3>Posts</h3>
<div *ngFor='let post of user.posts' class='post'>
<h4>{{ post.title }}</h4>
<p>{{ post.content }}</p>
<small>{{ post.createdAt | date }}</small>
<button (click)='deletePost(post.id)'>Delete</button>
</div>
</div>
<div class='create-post'>
<h3>Create New Post</h3>
<input
[(ngModel)]='newPost.title'
placeholder='Post title'
class='form-control'
>
<textarea
[(ngModel)]='newPost.content'
placeholder='Post content'
class='form-control'
></textarea>
<button (click)='createPost()'>Create Post</button>
</div>
</div>
</div>
`
})
export class UserProfileComponent implements OnInit {
user: any = null
loading = false
error = ''
newPost = { title: '', content: '' }
constructor(private graphql: GraphQLService) {}
ngOnInit() {
this.loadUser('user-123')
}
loadUser(id: string) {
this.loading = true
this.error = ''
this.graphql.getUser(id).subscribe({
next: (response) => {
this.user = response.user
this.loading = false
},
error: (err) => {
this.error = err.message
this.loading = false
}
})
}
createPost() {
if (!this.newPost.title || !this.newPost.content) {
return
}
this.graphql.createPost(
this.newPost.title,
this.newPost.content
).subscribe({
next: (response) => {
this.user.posts.push(response.createPost)
this.newPost = { title: '', content: '' }
},
error: (err) => {
this.error = err.message
}
})
}
deletePost(id: string) {
this.graphql.deletePost(id).subscribe({
next: () => {
this.user.posts = this.user.posts.filter((p: any) => p.id !== id)
},
error: (err) => {
this.error = err.message
}
})
}
}
Here the GraphQL service wraps HttpClient for sending GraphQL queries as POST requests with query and variables. TypeScript interfaces define expected response shapes for type safety. The query method handles error checking from GraphQL response errors array. Variables passed as separate object enable parameterized queries for dynamic values. Queries use GraphQL syntax specifying exact fields needed, reducing over-fetching. Mutations modify server data using mutation keyword instead of query. The map operator extracts data property from GraphQL response structure.
Best Practice Note:
This is the GraphQL integration approach we use in CoreUI Angular projects when backend provides GraphQL API instead of REST endpoints. Fragment queries for reusable field selections across multiple queries, implement request batching to combine multiple queries into single HTTP request, and consider Apollo Angular client for advanced features like caching, subscriptions, and optimistic UI updates when GraphQL becomes primary API strategy.



