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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author