From 70309340aadc4ffb9a1621d65f815ded6de1ba40 Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Sat, 13 Jul 2024 23:46:02 +0100 Subject: [PATCH 1/6] docs: Add comprehensive GraphQL in Angular guide with 5 approaches --- graphql/graphql-angular-clients.md | 1045 ++++++++++++++++++++++++++++ package-lock.json | 1 + 2 files changed, 1046 insertions(+) create mode 100644 graphql/graphql-angular-clients.md diff --git a/graphql/graphql-angular-clients.md b/graphql/graphql-angular-clients.md new file mode 100644 index 0000000000..7a3eb813de --- /dev/null +++ b/graphql/graphql-angular-clients.md @@ -0,0 +1,1045 @@ +--- +title: "GraphQL in Angular: 5 Best Approaches for Data Fetching" +description: "Explore Apollo Angular, Urql, and other methods for efficient GraphQL integration in Angular applications, with detailed comparisons and error handling strategies." +sidebar_label: "GraphQL with Angular" +slug: graphql-angular-client +--- + +## Introduction + +Angular developers often face the challenge of efficiently fetching and managing data from GraphQL APIs. This comprehensive guide dives into five powerful approaches for integrating GraphQL into your Angular applications. We'll explore everything from full-featured client libraries to lightweight solutions, using a practical example of fetching post data to demonstrate each method's strengths and nuances. + +Our journey will take us through Apollo Angular, Urql, GraphQL-Request, Axios, and the native Fetch API, each offering unique advantages for different project needs. Whether you're building a small-scale application or a complex enterprise system, this guide will equip you with the knowledge to choose the best GraphQL integration method for your Angular project. + +We'll not only cover the implementation details but also delve into error handling strategies, providing you with robust solutions to gracefully manage various API-related issues. By the end of this guide, you'll have a clear understanding of how to leverage GraphQL in Angular, complete with code snippets, real-world analogies, and a detailed comparison table to aid your decision-making process. + +So, buckle up and get ready to supercharge your Angular applications with the power of GraphQL! + +_**NB**: We are not using the traditional NgModule-based Angular applications instead we will be using the newer standalone component approach; below is the version of angular cli version used thoughout the guide._ + +```shell + ng version + + _ _ ____ _ ___ + / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| + / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | + / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | + /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| + |___/ + + + Angular CLI: 18.0.7 + Node: 20.12.2 + Package Manager: npm 10.5.0 + OS: linux x64 + + Angular: 18.0.6 + ... animations, common, compiler, compiler-cli, core, forms + ... platform-browser, platform-browser-dynamic, platform-server + ... router + + Package Version + --------------------------------------------------------- + @angular-devkit/architect 0.1800.7 + @angular-devkit/build-angular 18.0.7 + @angular-devkit/core 18.0.7 + @angular-devkit/schematics 18.0.7 + @angular/cli 18.0.7 + @angular/ssr 18.0.7 + @schematics/angular 18.0.7 + rxjs 7.8.1 + typescript 5.4.5 + zone.js 0.14.7 +``` +We'll be using a Tailcall backend that wraps the JSONPlaceholder API, providing a GraphQL interface to RESTful data. + +### 🛠️ Project Setup + +First, let's set up our Angular project: + +```bash + ng new angular-graphql-tailcall-showcase + cd angular-graphql-tailcall-showcase +``` + +### 🔧 Tailcall Backend Configuration + +Create a tailcall directory in the project root and add a jsonplaceholder.graphql file: + +```graphql + # File: tailcall/jsonplaceholder.graphql + + schema + @server(port: 8000, hostname: "0.0.0.0") + @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42) { + query: Query + } + + type Query { + posts: [Post] @http(path: "/posts") + user(id: Int!): User @http(path: "/users/{{.args.id}}") + } + + type User { + id: Int! + name: String! + username: String! + email: String! + phone: String + website: String + } + + type Post { + id: Int! + userId: Int! + title: String! + body: String! + user: User @http(path: "/users/{{.value.userId}}") + } +``` + +To start the Tailcall server, run: + +```sh + tailcall start ./tailcall/jsonplaceholder.graphql +``` + +### 1. Apollo Angular - The Luxury Sports Car of GraphQL Clients + +First up on our list is Apollo Angular. If GraphQL clients were cars, Apollo would be the Tesla of the bunch - sleek, powerful, and packed with features you didn't even know you needed. Let's pop the hood and see what makes this beauty purr! + +#### Installation and Integration Steps + +Before we can take Apollo for a spin, we need to get it set up in our garage (I mean, project). Here's how: + +1. **Install the necessary packages:**: + + ```sh + npm install apollo-angular @apollo/client graphql + ``` + +2. **Configure Apollo in your `app.config.ts`**: + + ```jsx + import { APOLLO_OPTIONS, ApolloModule } from 'apollo-angular'; + import { HttpLink } from 'apollo-angular/http'; + import { InMemoryCache } from '@apollo/client/core'; + + // In your ApplicationConfig + { + providers: [ + importProvidersFrom(ApolloModule), + { + provide: APOLLO_OPTIONS, + useFactory: (httpLink: HttpLink) => ({ + cache: new InMemoryCache(), + link: httpLink.create({ + uri: '/graphql', + }), + }), + deps: [HttpLink], + }, + ], + } + ``` + +3. **Code Snippets** +Now that we've got our Apollo rocket fueled up, let's see it in action! Here's a component that fetches a list of posts using Apollo in `src/app/apollo-angular/post-list.component.ts`: + +```typescript + import { Component, OnDestroy } from '@angular/core'; + import { CommonModule } from '@angular/common'; + import { Apollo, gql } from 'apollo-angular'; + import { ChangeDetectorRef } from '@angular/core'; + import { catchError, takeUntil, mergeMap } from 'rxjs/operators'; + import { Subject, of, throwError } from 'rxjs'; + + @Component({ + selector: 'app-apollo-post-list', + standalone: true, + imports: [CommonModule], + template: ` +

Posts (Apollo Angular)

+ + + + + +
+ {{ error }} +
+ `, + styles: [` + .error-message { + color: red; + margin-top: 10px; + } + `], + }) + export class ApolloPostListComponent implements OnDestroy { + // ... (component properties and constructor) + + fetchPosts() { + this.loading = true; + this.error = null; + this.posts = []; + + let query = gql` + query GetPosts($limit: Int) { + posts(limit: $limit) { + id + title + ${this.simulateGraphQLError ? 'nonExistentField' : ''} + } + } + `; + + this.apollo + .watchQuery({ + query: query, + variables: { + limit: 10, + }, + }) + .valueChanges.pipe( + takeUntil(this.unsubscribe$), + mergeMap((result) => { + if (this.simulateNetworkError) { + return throwError(() => new Error('Simulated network error')); + } + if (this.simulateUnexpectedError) { + throw new Error('Simulated unexpected error'); + } + return of(result); + }), + catchError((error) => { + this.handleError(error); + return of(null); + }) + ) + .subscribe({ + next: (result: any) => { + if (result) { + this.posts = result.data?.posts || []; + } + this.loading = false; + this.cdr.detectChanges(); + }, + error: (error) => this.handleError(error), + complete: () => { + this.loading = false; + this.cdr.detectChanges(); + }, + }); + } + + // ... (error handling and simulation methods) + } +``` + +Wow, would you look at that beauty? 😍 This component is like a finely tuned engine, ready to fetch your posts with the precision of a Swiss watch. Let's break down what's happening here: + +1. We're using Apollo's watchQuery method to fetch our posts. It's like having a personal assistant who's always on the lookout for the latest data. +2. We've got some nifty error simulation methods. It's like having a crash test dummy for your data fetching - you can deliberately cause errors to see how your app handles them. Safety first, right? +3. The mergeMap operator is our traffic controller, deciding whether to let the data through or throw an error based on our simulation flags. +4. We're using takeUntil with a Subject to ensure we clean up our subscriptions when the component is destroyed. It's like having an eco-friendly car that doesn't leave any pollution (memory leaks) behind! +5. The template gives us a simple UI to fetch posts and trigger various error scenarios. It's like having a dashboard with different buttons to test your car's performance. + +#### Error Handling + +Speaking of errors, Apollo doesn't just fetch data - it's got your back when things go wrong. Check out this error handling logic: + +```typescript + private handleError(error: any) { + this.loading = false; + if (error.networkError) { + this.error = 'Network error. Please check your internet connection.'; + } else if (error.graphQLErrors) { + this.error = `GraphQL error: ${error.graphQLErrors + .map((e: { message: any }) => e.message) + .join(', ')}`; + } else { + this.error = 'An unexpected error occurred. Please try again later.'; + } + console.error('Error fetching posts', error); + this.cdr.detectChanges(); + } +``` + +This error handler is like having a built-in mechanic. Whether it's a network issue (like running out of gas) or a GraphQL error (engine trouble), it's got you covered with user-friendly messages. + +#### Wrapping Up Apollo Angular + +And there you have it, folks! Apollo Angular - the smooth-riding, feature-packed, error-handling marvel of the GraphQL world. It's like driving a luxury car with a supercomputer onboard. + + +### 2. Axios - The Versatile Muscle Car of HTTP Clients + +If Apollo Angular is the luxury sports car of GraphQL clients, then Axios is like a classic muscle car - powerful, versatile, and ready to handle anything you throw at it. It might not have all the GraphQL-specific bells and whistles, but boy, can it perform! + +#### 1. Installation and Integration Steps + +Before we hit the gas, let's get our Axios engine installed and tuned up: + +1. **Instalations**. + +First, rev up your terminal and run: + + ```bash + npm install axios + ``` + +Unlike Apollo, Axios doesn't need any special configuration in your app.config.ts. It's more of a plug-and-play solution. Just import it where you need it, and you're good to go! + +1. **Code Snippets** + +Now, below we implement data fetching using axios in `src/app/axios-angular/post-list.component.ts`: + +```typescript + import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; + import { CommonModule } from '@angular/common'; + import axios, { AxiosInstance, AxiosError } from 'axios'; + + @Component({ + selector: 'app-axios-post-list', + standalone: true, + imports: [CommonModule], + template: ` +

Posts (Axios Angular)

+ + + + + +
+ {{ error }} +
+ `, + // ... (styles omitted for brevity) + }) + export class AxiosPostsListsComponent implements OnInit { + private client: AxiosInstance; + posts: any[] = []; + loading = false; + error: string | null = null; + + // Error simulation flags + private simulateNetworkError = false; + private simulateGraphQLError = false; + private simulateUnexpectedError = false; + + constructor(private cdr: ChangeDetectorRef) { + this.client = axios.create({ + baseURL: '/graphql', + headers: { + 'Content-Type': 'application/json', + }, + }); + } + + ngOnInit() { + // Add a request interceptor + this.client.interceptors.request.use( + (config) => { + if (this.simulateNetworkError) { + return Promise.reject(new Error('Simulated network error')); + } + return config; + }, + (error) => Promise.reject(error) + ); + } + + private GET_DATA = ` + query GetPosts($limit: Int) { + posts(limit: $limit) { + id + title + ${this.simulateGraphQLError ? 'nonExistentField' : ''} + } + } + `; + + async query(queryString: string, variables: any = {}) { + try { + if (this.simulateUnexpectedError) { + throw new Error('Simulated unexpected error'); + } + const response = await this.client.post('', { + query: queryString, + variables, + }); + return response.data; + } catch (error) { + this.handleError(error); + throw error; + } + } + + async fetchPosts() { + this.loading = true; + this.error = null; + this.posts = []; + this.cdr.detectChanges(); + + try { + const result = await this.query(this.GET_DATA, { limit: 10 }); + this.posts = result.data.posts; + this.loading = false; + this.cdr.detectChanges(); + } catch (error) { + // Error is already handled in query method + this.loading = false; + this.cdr.detectChanges(); + } + } + + // ... (error handling and simulation methods omitted for brevity) + } +``` + +This Axios-powered component is revving up to fetch those posts faster than you can say "GraphQL"! Let's break down what's happening in this high-octane code: + +1. We're creating an Axios instance in the constructor. It's like customizing your car with a specific paint job (baseURL) and some cool decals (headers). +2. The ngOnInit method adds a request interceptor. Think of it as a nitrous oxide system - it can give your requests an extra boost or, in this case, simulate a network error if you want to test your error handling. +3. Our query method is like the engine of this muscle car. It takes a GraphQL query string and variables, then fires off the request. If something goes wrong, it calls our trusty mechanic (the handleError method). +4. The fetchPosts method is where the rubber meets the road. It calls our query method with the posts query, then updates our component state with the results. +5. We've got our error simulation methods, just like in the Apollo example. It's like having different test tracks for your muscle car - you can simulate various error conditions to make sure your code can handle any bumps in the road. + +#### 2. Error Handling + +Now, let's talk about handling of errors: + +```typescript + private handleError(error: any) { + if (axios.isAxiosError(error)) { + const axiosError = error as AxiosError; + if (axiosError.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + this.error = `Server error: ${axiosError.response.status} ${axiosError.response.statusText}`; + } else if (axiosError.request) { + // The request was made but no response was received + this.error = 'Network error. Please check your internet connection.'; + } else { + // Something happened in setting up the request that triggered an Error + this.error = 'An unexpected error occurred. Please try again later.'; + } + } else if (error.graphQLErrors) { + this.error = `GraphQL error: ${error.graphQLErrors + .map((e: any) => e.message) + .join(', ')}`; + } else { + this.error = 'An unexpected error occurred. Please try again later.'; + } + console.error('Error fetching posts:', error); + } +``` + +This error handler is like the world's best shock absorber system. Whether you hit a pothole (network error), take a wrong turn (server error), or your engine misfires (unexpected error), it's got you covered with user-friendly messages. It even handles those tricky GraphQL-specific errors! + +#### Wrapping Up Axios + +And there you have it, Axios - the muscle car of HTTP clients, now tuned up to handle GraphQL queries with style. It might not have all the GraphQL-specific features of Apollo, but it's a powerhouse that can handle just about anything you throw at it. +Axios shines when you need a lightweight, versatile solution that can handle both REST and GraphQL APIs. It's like having a car that's equally at home on the racetrack and the city streets. Plus, if you're already familiar with Axios from REST API work, the learning curve here is as smooth as a freshly paved highway. + +### 3. Fetch API - The Lean, Mean, JavaScript Machine + +If Apollo was our luxury sports car and Axios our muscle car, then the Fetch API is like a nimble, lightweight motorcycle. It's built right into modern browsers, requires no external libraries, and can zip through traffic with ease. Let's see how this speed demon handles our GraphQL queries! + +#### 1. Installation and Integration Steps + +Here's the beauty of the Fetch API - there's nothing to install! 🎉 It's like finding out your new apartment comes with a free motorcycle in the garage. Just hop on and ride! + +#### 2. Code Snippets + +```typescript + import { Component, ChangeDetectorRef } from '@angular/core'; + import { CommonModule } from '@angular/common'; + + @Component({ + selector: 'app-fetch-post-list', + standalone: true, + imports: [CommonModule], + template: ` +

Posts (Fetch Angular)

+ + + + + +
+ {{ error }} +
+ `, + // ... (styles omitted for brevity) + }) + export class FetchPostListComponent { + private endpoint = '/graphql'; + posts: any[] = []; + loading = false; + error: string | null = null; + + // Error simulation flags + private simulateNetworkError = false; + private simulateGraphQLError = false; + private simulateUnexpectedError = false; + + constructor(private cdr: ChangeDetectorRef) {} + + private GET_DATA = ` + query GetPosts($limit: Int) { + posts(limit: $limit) { + id + title + ${this.simulateGraphQLError ? 'nonExistentField' : ''} + } + } + `; + + async query(queryString: string, variables: any = {}) { + if (this.simulateNetworkError) { + throw new Error('Simulated network error'); + } + + if (this.simulateUnexpectedError) { + throw new Error('Simulated unexpected error'); + } + + try { + const response = await fetch(this.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: queryString, + variables, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + if (result.errors) { + throw new Error(result.errors.map((e: any) => e.message).join(', ')); + } + + return result; + } catch (error) { + this.handleError(error); + throw error; + } + } + + async fetchPosts() { + this.loading = true; + this.error = null; + this.posts = []; + this.cdr.detectChanges(); + + try { + const result = await this.query(this.GET_DATA, { limit: 10 }); + this.posts = result.data.posts; + this.loading = false; + this.cdr.detectChanges(); + } catch (error) { + // Error is already handled in query method + this.loading = false; + this.cdr.detectChanges(); + } + } + + // ... (error handling and simulation methods omitted for brevity) + } +``` + +This Fetch-powered component is leaner than a greyhound and faster than a caffeinated cheetah! Let's break down what's happening in this high-speed code: + +1. No imports needed for Fetch - it's built right into the browser. It's like having a motorcycle that doesn't need gas! +2. Our query method is the engine of this speed machine. It takes a GraphQL query string and variables, then zooms off to fetch the data. +3. We're using async/await syntax, which makes our asynchronous code read like a smooth ride down the highway. +4. The fetchPosts method is where we kick into high gear. It calls our query method with the posts query, then updates our component state with the results. +5. We've still got our error simulation methods. It's like having different obstacle courses for our motorcycle - we can test how it handles in various tricky situations. + +#### Error Handling + +Now, let's talk about the suspension system of our Fetch motorcycle - the error handling: + +```typescript + private handleError(error: any) { + if (error instanceof TypeError && error.message === 'Failed to fetch') { + this.error = 'Network error. Please check your internet connection.'; + } else if (error instanceof Error) { + if (error.message.includes('GraphQL error')) { + this.error = `GraphQL error: ${error.message}`; + } else if (error.message.startsWith('HTTP error!')) { + this.error = `Server error: ${error.message}`; + } else { + this.error = 'An unexpected error occurred. Please try again later.'; + } + } else { + this.error = 'An unexpected error occurred. Please try again later.'; + } + console.error('Error fetching posts:', error); + } +``` + +This error handler efficiently manages various error types. It provides user-friendly messages for network issues, server errors, unexpected problems, and GraphQL-specific errors, ensuring a smooth user experience even when things go wrong. + +#### Wrapping Up Fetch API + +And there you have it, The Fetch API - the nimble, lightweight motorcycle of HTTP clients, now revved up to handle GraphQL queries with style. It might not have all the bells and whistles of Apollo or the versatility of Axios, but it's fast, it's built-in, and it gets the job done with minimal fuss. + +Fetch shines when you need a lightweight, no-dependency solution that can handle both REST and GraphQL APIs. It's like having a motorcycle that's equally at home zipping through city traffic or cruising on the open highway. Plus, if you're looking to keep your project dependencies to a minimum, Fetch is your go-to ride. + + +### 4. GraphQL Request - The Precision-Engineered Sports Car + +If Apollo was our luxury sedan, Axios our muscle car, and Fetch our nimble motorcycle, then GraphQL Request is like a finely-tuned sports car. It's designed specifically for GraphQL, offering a perfect balance of simplicity and power. Let's see how this beauty handles our data-fetching curves! + +1. **Installation and Integration Steps** +Before we hit the track, let's get our GraphQL Request engine installed: + +```bash + npm install graphql-request graphql +``` + +No special configuration needed in your app.config.ts. Just import it in your component, and you're ready to race! + +2. **Code Snippets** + +Now, let's pop the hood and examine our GraphQL Request-powered component: + +```typescript + import { Component, ChangeDetectorRef } from '@angular/core'; + import { CommonModule } from '@angular/common'; + import { GraphQLClient, gql, ClientError } from 'graphql-request'; + + @Component({ + selector: 'app-graphql-request-post-list', + standalone: true, + imports: [CommonModule], + template: ` +

Posts (Graphql Request Angular)

+ + + + + +
+ {{ error }} +
+ `, + // ... (styles omitted for brevity) + }) + export class GraphqlRequestPostListComponent { + private client: GraphQLClient; + posts: any[] = []; + loading = false; + error: string | null = null; + + // Error simulation flags + private simulateNetworkError = false; + private simulateGraphQLError = false; + private simulateUnexpectedError = false; + + constructor(private cdr: ChangeDetectorRef) { + this.client = new GraphQLClient('http://localhost:4200/graphql'); + } + + private GET_DATA = gql` + query GetPosts($limit: Int) { + posts(limit: $limit) { + id + title + ${this.simulateGraphQLError ? 'nonExistentField' : ''} + } + } + `; + + async fetchPosts() { + this.loading = true; + this.error = null; + this.posts = []; + this.cdr.detectChanges(); + + try { + if (this.simulateNetworkError) { + throw new Error('Simulated network error'); + } + + if (this.simulateUnexpectedError) { + throw new Error('Simulated unexpected error'); + } + + const result: any = await this.client.request(this.GET_DATA, { + limit: 10, + }); + this.posts = result.posts; + this.loading = false; + this.cdr.detectChanges(); + } catch (error) { + this.handleError(error); + this.loading = false; + this.cdr.detectChanges(); + } + } + + // ... (error handling and simulation methods omitted for brevity) + } +``` + +Let's break down what's happening in this high-performance code: + +1. We're importing GraphQLClient and gql from graphql-request. It's like having a custom-built engine and transmission, specifically designed for GraphQL roads. +2. In the constructor, we're initializing our GraphQLClient. It's like setting up the onboard computer of our sports car, telling it exactly where to go for our data. +3. Our GET_DATA query is defined using the gql tag. It's like programming the GPS with the exact route we want to take. +4. The fetchPosts method is where we put the pedal to the metal. We're using the client.request method, which is like engaging the launch control on our sports car - it handles everything for us, from acceleration to gear shifts. +5. We've still got our error simulation methods. It's like having different road conditions we can simulate - wet roads, oil slicks, you name it! + +#### Error Handling + +Now, let's talk about the advanced traction control system of our GraphQL Request sports car - the error handling: + +```typescript + private handleError(error: any) { + if (error instanceof ClientError) { + if (error.response.errors) { + // GraphQL errors + this.error = `GraphQL error: ${error.response.errors + .map((e) => e.message) + .join(', ')}`; + } else { + // Network errors or other HTTP errors + this.error = `Network error: ${error.response.status} ${error.response['statusText']}`; + } + } else if (error instanceof Error) { + if (error.message === 'Simulated network error') { + this.error = 'Network error. Please check your internet connection.'; + } else { + this.error = 'An unexpected error occurred. Please try again later.'; + } + } else { + this.error = 'An unexpected error occurred. Please try again later.'; + } + console.error('Error fetching posts:', error); + } +``` + + +This error handler is like having the world's best traction control and stability management system. Whether you hit a patch of black ice (network error), take a corner too fast (GraphQL error), or encounter an unexpected obstacle (other errors), it's got you covered with user-friendly messages. It even distinguishes between different types of errors, giving you precise control over how to handle each situation. + +#### Wrapping Up GraphQL Request + +And there you have it, folks! GraphQL Request - the precision-engineered sports car of GraphQL clients. It's streamlined, efficient, and designed specifically for the twists and turns of GraphQL queries. +GraphQL Request shines when you need a lightweight, GraphQL-specific solution that offers more than Fetch but doesn't require the full ecosystem of Apollo. It's like having a sports car that's perfect for both daily commutes and weekend track days. Plus, its simplicity makes it a joy to work with, especially for smaller to medium-sized projects. + +### 5. Urql in Angular + +#### Installation and Integration Steps + +First things first, let's get our hands dirty with some installation magic. To bring Urql into your Angular project, you'll need to wave your command line wand and chant: + +```bash + npm install @urql/core graphql +``` +We need to set up our Urql client. + +#### Code Snippets and Explanation + +Let's break down our UrqlPostListComponent which you'll create following the same format above and solder structure: + +```typescript + import { createClient, fetchExchange, cacheExchange, Client } from '@urql/core'; + + // ... other imports + + export class UrqlPostListComponent { + client: Client; + + constructor(private cdr: ChangeDetectorRef) { + this.client = createClient({ + url: 'http://localhost:4200/graphql', + exchanges: [cacheExchange, fetchExchange], + }); + } + + // ... rest of the component + } +``` + +Here, we're setting up our Urql client faster than you can say "GraphQL". We're telling it where to find our GraphQL endpoint and which exchanges to use. Think of exchanges as middleware for your GraphQL requests - they're like bouncers at a club, deciding how to handle incoming and outgoing traffic. + +Now, let's look at how we're fetching posts: +```typescript + getPostsQuery = gql` + query GetPosts($limit: Int) { + posts(limit: $limit) { + id + title + } + } + `; + + fetchPosts() { + this.loading = true; + this.error = null; + this.posts = []; + this.cdr.detectChanges(); + + this.client + .query(this.getPostsQuery, { limit: 10 }) + .toPromise() + .then((result) => { + if (result.error) { + this.handleError(result.error); + } else { + this.posts = result.data?.posts || []; + this.loading = false; + this.cdr.detectChanges(); + } + }) + .catch((error) => this.handleError(error)) + .finally(() => { + this.loading = false; + this.cdr.detectChanges(); + }); + } +``` + +This fetchPosts method is where the magic happens. We're using Urql's query method to fetch our posts, handling the result like a pro juggler. If there's an error, we toss it to our error handler. If it's successful, we update our posts faster than you can say "data fetched"! + +#### Error Handling +Now, let's talk about error handling. In the world of APIs, errors are like unexpected plot twists in a movie - they keep things interesting, but you need to know how to handle them: + +```typescript + private handleError(error: any) { + this.loading = false; + if (error instanceof CombinedError) { + if (error.networkError) { + this.error = 'Network error. Please check your internet connection.'; + } else if (error.graphQLErrors.length > 0) { + this.error = `GraphQL error: ${error.graphQLErrors + .map((e) => e.message) + .join(', ')}`; + } + } else if (error instanceof Error) { + this.error = `An unexpected error occurred: ${error.message}`; + } else { + this.error = 'An unexpected error occurred. Please try again later.'; + } + console.error('Error fetching posts:', error); + this.cdr.detectChanges(); + } +``` + +This error handler is like a Swiss Army knife for API errors. Network error? We've got you covered. GraphQL error? No problem. Unexpected error that makes you question the nature of reality? We handle that too! +Why Choose Urql? +You might be wondering, "Why should I choose Urql over other options?" Well, let me tell you, Urql is like that cool, efficient friend who always knows the best way to get things done: + +1. **Lightweight**: Urql is as light as a feather, which means your app won't feel like it's carrying extra baggage. +2. **Flexible**: It's adaptable to various use cases, like a chameleon in the coding world. +3. **Great Developer Experience**: With Urql, you'll feel like you're coding with a tailwind, not against a headwind. + +#### Real-world Analogy + +If Apollo Client is like a fully-equipped Swiss Army knife, Urql is more like a sleek, modern multi-tool. It's streamlined, efficient, and gets the job done without unnecessary complexity. You know, like that one friend who always seems to have exactly what you need, no more, no less. +So there you have it, folks! Urql in Angular: a match made in developer heaven. It's flexible, efficient, and handles errors like a boss. Whether you're building a small project or a complex application, Urql's got your back. +Remember, in the world of GraphQL clients, there's no one-size-fits-all solution. It's all about finding the right tool for your specific needs. So go forth and GraphQL with confidence, knowing you've got Urql in your toolbelt! + +1. **Apollo Angular**: A comprehensive GraphQL client that provides robust features like caching and state management. +2. **Urql**: A lightweight and flexible GraphQL client with a focus on simplicity and customization. +3. **GraphQL-Request**: A minimal GraphQL client supporting Node and browsers. +4. **Axios**: A promise-based HTTP client that can be used to send GraphQL queries. +5. **Fetch API**: The native browser API for making HTTP requests, used here for GraphQL queries. + + +## Detailed Comparison Table + +| Method | Bundle Size (minified + gzip)* | Learning Curve | Caching Capabilities | Community Support | Additional Features | +|------------------|---------------------------------|----------------|----------------------------------------|-------------------|----------------------------------------| +| Apollo Angular | ~2kB | Moderate | Extensive (InMemoryCache, customizable) | High | State management, optimistic UI updates | +| Urql | ~10.2 KB | Low | Moderate (Document caching) | Moderate | Extensible architecture, lightweight | +| GraphQL-Request | Unknown | Low | None (Minimal client) | Moderate | Simplicity, works in Node and browsers | +| Axios | ~13.2 KB | Low | None (HTTP client only) | High | Familiar HTTP handling, interceptors | +| Fetch API | 0 KB (Browser built-in) | Low | None (Native API) | High | No additional dependency, widely supported | + +(*) Bundle sizes are approximate and may vary based on version and configuration. Values are culled from bundlephobia.com where available. + +### Notes: + +- **Apollo Angular**: Offers the most comprehensive feature set but comes with a larger bundle size and steeper learning curve. +- **Urql**: Provides a good balance between features and bundle size, with a focus on simplicity. +- **GraphQL-Request**: Minimal client ideal for simple use cases where advanced features aren't needed. +- **Axios**: Not a GraphQL-specific solution, but familiar to many developers and versatile for various HTTP requests. +- **Fetch API**: Native browser API, no additional bundle size, but requires more manual work for GraphQL operations. + +This table should help developers choose the right method based on their specific project needs, considering factors like bundle size, learning curve, caching capabilities, community support, and additional features. + +### Caching Capabilities + +1. **Apollo Angular** + - Extensive caching capabilities through InMemoryCache + - Normalization of data for efficient storage and retrieval + - Customizable cache policies (cache-first, network-only, etc.) + - Automatic cache updates on mutations + - Support for pagination and optimistic UI updates + - Ability to manually update and read from the cache + +2. **Urql** + - Document caching by default + - Customizable caching through exchangeable cache implementations + - Supports normalized caching with additional setup + - Cache invalidation and updates through GraphQL mutations + - Simpler caching model compared to Apollo, focusing on ease of use + +3. **GraphQL-Request** + - No built-in caching mechanism + - Requires manual implementation of caching if needed + - Can be combined with external caching solutions or state management libraries + +4. **Axios** + - No built-in GraphQL-specific caching + - Can implement HTTP-level caching (e.g., using headers) + - Requires manual implementation of application-level caching + - Can be combined with state management libraries for more sophisticated caching + +5. **Fetch API** + - No built-in GraphQL-specific caching + - Supports basic HTTP caching through cache-control headers + - Requires manual implementation of application-level caching + - Can be combined with other libraries or custom solutions for more advanced caching + +In summary, Apollo Angular offers the most robust out-of-the-box caching solution, followed by Urql with its flexible caching system. GraphQL-Request, Axios, and Fetch API do not provide GraphQL-specific caching, requiring developers to implement their own caching strategies or integrate with other libraries for advanced caching needs. + +When choosing an approach, consider your application's complexity, performance requirements, and willingness to manage caching manually versus leveraging built-in solutions. + + +## Common Issues and Resolutions + +1. **Apollo Angular** + + Issue: Cache inconsistencies after mutations + Resolution: + - Ensure proper cache updates in mutation's `update` function + - Use `refetchQueries` option to refresh related queries + - Implement `optimisticResponse` for immediate UI updates + + Issue: Over-fetching data + Resolution: + - Utilize fragments for reusable field selections + - Implement proper query splitting for components + - Use `@connection` directive for pagination to avoid refetching all data + +2. **Urql** + + Issue: Stale data after mutations + Resolution: + - Use the `cache-and-network` request policy + - Implement cache updates in mutation's `updates` option + - Utilize the `refocusExchange` for automatic refetching on window focus + + Issue: Complex state management + Resolution: + - Combine Urql with external state management libraries like NgRx if needed + - Leverage Urql's `useQuery` and `useMutation` hooks for simpler state handling + +3. **GraphQL-Request** + + Issue: Lack of automatic caching + Resolution: + - Implement manual caching using services or state management libraries + - Use HTTP caching headers for basic caching needs + - Consider switching to Apollo or Urql for more complex applications + + Issue: Error handling complexities + Resolution: + - Implement a centralized error handling service + - Use TypeScript for better type checking and error prevention + - Wrap GraphQL-Request calls in try-catch blocks for granular error handling + +4. **Axios** + + Issue: Constructing complex GraphQL queries + Resolution: + - Use template literals for dynamic query construction + - Implement a query builder utility for complex queries + - Consider using a GraphQL-specific library for very complex schemas + + Issue: Handling GraphQL errors + Resolution: + - Check for `errors` array in the response body + - Implement custom error classes for different GraphQL error types + - Use interceptors for global error handling + +5. **Fetch API** + + Issue: Verbose syntax for GraphQL operations + Resolution: + - Create utility functions to abstract common GraphQL operations + - Use TypeScript interfaces for better type safety and autocompletion + - Consider using a lightweight wrapper around Fetch for GraphQL specifics + + Issue: Limited built-in features + Resolution: + - Implement custom middleware for features like retries and caching + - Use external libraries for advanced features (e.g., Observable support) + - Create a custom Angular service to encapsulate Fetch API logic + +General Resolutions: + +- Implement proper error boundaries in your Angular components +- Use TypeScript for better type checking and IDE support +- Leverage Angular's HttpInterceptors for global request/response handling +- Implement proper loading states to improve user experience during data fetching +- Use environment variables for GraphQL endpoint configuration + +By addressing these common issues, developers can create more robust and efficient GraphQL implementations in their Angular applications, regardless of the chosen approach. + +## Conclusion + +As we've journeyed through the landscape of GraphQL integration in Angular, we've explored five distinct approaches, each with its own strengths and considerations. Let's recap and draw some final insights: + +1. **Apollo Angular** emerges as the powerhouse solution, offering a comprehensive feature set including robust caching, state management, and optimistic UI updates. It's ideal for large-scale applications with complex data requirements, though it comes with a steeper learning curve and larger bundle size. + +2. **Urql** strikes a balance between functionality and simplicity. Its lightweight nature and extensible architecture make it an excellent choice for projects that need flexibility without the full weight of Apollo. It's particularly suitable for medium-sized applications or teams that prefer a more customizable approach. + +3. **GraphQL-Request** shines in its simplicity. For small projects or microservices where basic GraphQL operations are all that's needed, it provides a no-frills solution with minimal overhead. However, it lacks built-in caching and advanced features, which may become limitations as your project grows. + +4. **Axios**, while not GraphQL-specific, leverages its widespread adoption and familiarity among developers. It's a solid choice for teams already using Axios in their stack or for projects that mix RESTful and GraphQL APIs. However, it requires more manual work for GraphQL-specific features. + +5. **Fetch API** represents the most lightweight approach, with zero additional bundle size. It's ideal for projects prioritizing minimal dependencies and maximum browser compatibility. However, it necessitates more boilerplate code and manual implementation of GraphQL-specific features. + +The choice between these approaches ultimately depends on your project's specific needs, your team's expertise, and your application's scalability requirements. Here are some final recommendations: + +- For large, data-intensive applications with complex requirements, Apollo Angular is likely your best bet. +- If you're looking for a lightweight yet capable solution, Urql offers an excellent middle ground. +- For smaller projects or microservices, GraphQL-Request or Fetch API might be sufficient. +- If your project involves a mix of REST and GraphQL APIs, consider Axios for its versatility. + +Remember, there's no one-size-fits-all solution. The best approach is the one that aligns with your project's needs and your team's capabilities. As your application evolves, don't hesitate to reassess and switch approaches if necessary. + +Whichever path you choose, GraphQL's power in providing flexible, efficient data fetching can significantly enhance your Angular applications. By understanding these different approaches, you're now equipped to make an informed decision and leverage GraphQL to its full potential in your Angular projects. + +Happy coding, and may your GraphQL queries be ever efficient! + diff --git a/package-lock.json b/package-lock.json index ebda2fdf19..b96dde3cb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22793,6 +22793,7 @@ } }, "publish-hashnode": { + "name": "blog-publisher", "version": "1.0.0", "license": "ISC", "dependencies": { From 89e44a7f6f0d8fa3c6dbe3649305a6fa6b531fb7 Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Sat, 13 Jul 2024 23:57:50 +0100 Subject: [PATCH 2/6] fix(docs): spelling --- graphql/graphql-angular-clients.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphql/graphql-angular-clients.md b/graphql/graphql-angular-clients.md index 7a3eb813de..d29d5dd19a 100644 --- a/graphql/graphql-angular-clients.md +++ b/graphql/graphql-angular-clients.md @@ -15,7 +15,7 @@ We'll not only cover the implementation details but also delve into error handli So, buckle up and get ready to supercharge your Angular applications with the power of GraphQL! -_**NB**: We are not using the traditional NgModule-based Angular applications instead we will be using the newer standalone component approach; below is the version of angular cli version used thoughout the guide._ +_**NB**: We are not using the traditional NgModule-based Angular applications instead we will be using the newer standalone component approach; below is the version of angular cli version used throughout the guide._ ```shell ng version @@ -285,7 +285,7 @@ If Apollo Angular is the luxury sports car of GraphQL clients, then Axios is lik Before we hit the gas, let's get our Axios engine installed and tuned up: -1. **Instalations**. +1. **Installations**. First, rev up your terminal and run: From cda93b731d3f9ef282a6a6a6a294f39e746b6fd3 Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Sun, 14 Jul 2024 00:03:43 +0100 Subject: [PATCH 3/6] fix(docs): prettier fix --- graphql/graphql-angular-clients.md | 875 ++++++++++++++++------------- 1 file changed, 476 insertions(+), 399 deletions(-) diff --git a/graphql/graphql-angular-clients.md b/graphql/graphql-angular-clients.md index d29d5dd19a..1b225fda30 100644 --- a/graphql/graphql-angular-clients.md +++ b/graphql/graphql-angular-clients.md @@ -51,6 +51,7 @@ _**NB**: We are not using the traditional NgModule-based Angular applications in typescript 5.4.5 zone.js 0.14.7 ``` + We'll be using a Tailcall backend that wraps the JSONPlaceholder API, providing a GraphQL interface to RESTful data. ### 🛠️ Project Setup @@ -58,8 +59,8 @@ We'll be using a Tailcall backend that wraps the JSONPlaceholder API, providing First, let's set up our Angular project: ```bash - ng new angular-graphql-tailcall-showcase - cd angular-graphql-tailcall-showcase +ng new angular-graphql-tailcall-showcase +cd angular-graphql-tailcall-showcase ``` ### 🔧 Tailcall Backend Configuration @@ -67,41 +68,44 @@ First, let's set up our Angular project: Create a tailcall directory in the project root and add a jsonplaceholder.graphql file: ```graphql - # File: tailcall/jsonplaceholder.graphql - - schema - @server(port: 8000, hostname: "0.0.0.0") - @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42) { - query: Query - } - - type Query { - posts: [Post] @http(path: "/posts") - user(id: Int!): User @http(path: "/users/{{.args.id}}") - } - - type User { - id: Int! - name: String! - username: String! - email: String! - phone: String - website: String - } - - type Post { - id: Int! - userId: Int! - title: String! - body: String! - user: User @http(path: "/users/{{.value.userId}}") - } +# File: tailcall/jsonplaceholder.graphql + +schema + @server(port: 8000, hostname: "0.0.0.0") + @upstream( + baseURL: "http://jsonplaceholder.typicode.com" + httpCache: 42 + ) { + query: Query +} + +type Query { + posts: [Post] @http(path: "/posts") + user(id: Int!): User @http(path: "/users/{{.args.id}}") +} + +type User { + id: Int! + name: String! + username: String! + email: String! + phone: String + website: String +} + +type Post { + id: Int! + userId: Int! + title: String! + body: String! + user: User @http(path: "/users/{{.value.userId}}") +} ``` To start the Tailcall server, run: ```sh - tailcall start ./tailcall/jsonplaceholder.graphql +tailcall start ./tailcall/jsonplaceholder.graphql ``` ### 1. Apollo Angular - The Luxury Sports Car of GraphQL Clients @@ -144,101 +148,115 @@ Before we can take Apollo for a spin, we need to get it set up in our garage (I ``` 3. **Code Snippets** -Now that we've got our Apollo rocket fueled up, let's see it in action! Here's a component that fetches a list of posts using Apollo in `src/app/apollo-angular/post-list.component.ts`: + Now that we've got our Apollo rocket fueled up, let's see it in action! Here's a component that fetches a list of posts using Apollo in `src/app/apollo-angular/post-list.component.ts`: ```typescript - import { Component, OnDestroy } from '@angular/core'; - import { CommonModule } from '@angular/common'; - import { Apollo, gql } from 'apollo-angular'; - import { ChangeDetectorRef } from '@angular/core'; - import { catchError, takeUntil, mergeMap } from 'rxjs/operators'; - import { Subject, of, throwError } from 'rxjs'; - - @Component({ - selector: 'app-apollo-post-list', - standalone: true, - imports: [CommonModule], - template: ` -

Posts (Apollo Angular)

- - - - -
    -
  • {{ post.title }}
  • -
-
- {{ error }} -
- `, - styles: [` - .error-message { +import {Component, OnDestroy} from "@angular/core" +import {CommonModule} from "@angular/common" +import {Apollo, gql} from "apollo-angular" +import {ChangeDetectorRef} from "@angular/core" +import { + catchError, + takeUntil, + mergeMap, +} from "rxjs/operators" +import {Subject, of, throwError} from "rxjs" + +@Component({ + selector: "app-apollo-post-list", + standalone: true, + imports: [CommonModule], + template: ` +

Posts (Apollo Angular)

+ + + + +
    +
  • {{ post.title }}
  • +
+
+ {{ error }} +
+ `, + styles: [ + ` + .error-message { color: red; margin-top: 10px; - } - `], - }) - export class ApolloPostListComponent implements OnDestroy { - // ... (component properties and constructor) + } + `, + ], +}) +export class ApolloPostListComponent implements OnDestroy { + // ... (component properties and constructor) - fetchPosts() { - this.loading = true; - this.error = null; - this.posts = []; + fetchPosts() { + this.loading = true + this.error = null + this.posts = [] - let query = gql` + let query = gql` query GetPosts($limit: Int) { posts(limit: $limit) { id title - ${this.simulateGraphQLError ? 'nonExistentField' : ''} + ${this.simulateGraphQLError ? "nonExistentField" : ""} } } - `; - - this.apollo - .watchQuery({ - query: query, - variables: { - limit: 10, - }, - }) - .valueChanges.pipe( - takeUntil(this.unsubscribe$), - mergeMap((result) => { - if (this.simulateNetworkError) { - return throwError(() => new Error('Simulated network error')); - } - if (this.simulateUnexpectedError) { - throw new Error('Simulated unexpected error'); - } - return of(result); - }), - catchError((error) => { - this.handleError(error); - return of(null); - }) - ) - .subscribe({ - next: (result: any) => { - if (result) { - this.posts = result.data?.posts || []; - } - this.loading = false; - this.cdr.detectChanges(); - }, - error: (error) => this.handleError(error), - complete: () => { - this.loading = false; - this.cdr.detectChanges(); - }, - }); - } + ` - // ... (error handling and simulation methods) - } + this.apollo + .watchQuery({ + query: query, + variables: { + limit: 10, + }, + }) + .valueChanges.pipe( + takeUntil(this.unsubscribe$), + mergeMap((result) => { + if (this.simulateNetworkError) { + return throwError( + () => new Error("Simulated network error"), + ) + } + if (this.simulateUnexpectedError) { + throw new Error("Simulated unexpected error") + } + return of(result) + }), + catchError((error) => { + this.handleError(error) + return of(null) + }), + ) + .subscribe({ + next: (result: any) => { + if (result) { + this.posts = result.data?.posts || [] + } + this.loading = false + this.cdr.detectChanges() + }, + error: (error) => this.handleError(error), + complete: () => { + this.loading = false + this.cdr.detectChanges() + }, + }) + } + + // ... (error handling and simulation methods) +} ``` Wow, would you look at that beauty? 😍 This component is like a finely tuned engine, ready to fetch your posts with the precision of a Swiss watch. Let's break down what's happening here: @@ -276,7 +294,6 @@ This error handler is like having a built-in mechanic. Whether it's a network is And there you have it, folks! Apollo Angular - the smooth-riding, feature-packed, error-handling marvel of the GraphQL world. It's like driving a luxury car with a supercomputer onboard. - ### 2. Axios - The Versatile Muscle Car of HTTP Clients If Apollo Angular is the luxury sports car of GraphQL clients, then Axios is like a classic muscle car - powerful, versatile, and ready to handle anything you throw at it. It might not have all the GraphQL-specific bells and whistles, but boy, can it perform! @@ -289,9 +306,9 @@ Before we hit the gas, let's get our Axios engine installed and tuned up: First, rev up your terminal and run: - ```bash - npm install axios - ``` +```bash +npm install axios +``` Unlike Apollo, Axios doesn't need any special configuration in your app.config.ts. It's more of a plug-and-play solution. Just import it where you need it, and you're good to go! @@ -300,110 +317,124 @@ Unlike Apollo, Axios doesn't need any special configuration in your app.config.t Now, below we implement data fetching using axios in `src/app/axios-angular/post-list.component.ts`: ```typescript - import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; - import { CommonModule } from '@angular/common'; - import axios, { AxiosInstance, AxiosError } from 'axios'; - - @Component({ - selector: 'app-axios-post-list', - standalone: true, - imports: [CommonModule], - template: ` -

Posts (Axios Angular)

- - - - -
    -
  • {{ post.title }}
  • -
-
- {{ error }} -
- `, - // ... (styles omitted for brevity) +import { + Component, + OnInit, + ChangeDetectorRef, +} from "@angular/core" +import {CommonModule} from "@angular/common" +import axios, {AxiosInstance, AxiosError} from "axios" + +@Component({ + selector: "app-axios-post-list", + standalone: true, + imports: [CommonModule], + template: ` +

Posts (Axios Angular)

+ + + + +
    +
  • {{ post.title }}
  • +
+
+ {{ error }} +
+ `, + // ... (styles omitted for brevity) +}) +export class AxiosPostsListsComponent implements OnInit { + private client: AxiosInstance + posts: any[] = [] + loading = false + error: string | null = null + + // Error simulation flags + private simulateNetworkError = false + private simulateGraphQLError = false + private simulateUnexpectedError = false + + constructor(private cdr: ChangeDetectorRef) { + this.client = axios.create({ + baseURL: "/graphql", + headers: { + "Content-Type": "application/json", + }, }) - export class AxiosPostsListsComponent implements OnInit { - private client: AxiosInstance; - posts: any[] = []; - loading = false; - error: string | null = null; - - // Error simulation flags - private simulateNetworkError = false; - private simulateGraphQLError = false; - private simulateUnexpectedError = false; - - constructor(private cdr: ChangeDetectorRef) { - this.client = axios.create({ - baseURL: '/graphql', - headers: { - 'Content-Type': 'application/json', - }, - }); - } + } - ngOnInit() { - // Add a request interceptor - this.client.interceptors.request.use( - (config) => { - if (this.simulateNetworkError) { - return Promise.reject(new Error('Simulated network error')); - } - return config; - }, - (error) => Promise.reject(error) - ); - } + ngOnInit() { + // Add a request interceptor + this.client.interceptors.request.use( + (config) => { + if (this.simulateNetworkError) { + return Promise.reject( + new Error("Simulated network error"), + ) + } + return config + }, + (error) => Promise.reject(error), + ) + } - private GET_DATA = ` + private GET_DATA = ` query GetPosts($limit: Int) { posts(limit: $limit) { id title - ${this.simulateGraphQLError ? 'nonExistentField' : ''} - } - } - `; - - async query(queryString: string, variables: any = {}) { - try { - if (this.simulateUnexpectedError) { - throw new Error('Simulated unexpected error'); + ${this.simulateGraphQLError ? "nonExistentField" : ""} } - const response = await this.client.post('', { - query: queryString, - variables, - }); - return response.data; - } catch (error) { - this.handleError(error); - throw error; } + ` + + async query(queryString: string, variables: any = {}) { + try { + if (this.simulateUnexpectedError) { + throw new Error("Simulated unexpected error") + } + const response = await this.client.post("", { + query: queryString, + variables, + }) + return response.data + } catch (error) { + this.handleError(error) + throw error } - - async fetchPosts() { - this.loading = true; - this.error = null; - this.posts = []; - this.cdr.detectChanges(); - - try { - const result = await this.query(this.GET_DATA, { limit: 10 }); - this.posts = result.data.posts; - this.loading = false; - this.cdr.detectChanges(); - } catch (error) { - // Error is already handled in query method - this.loading = false; - this.cdr.detectChanges(); - } + } + + async fetchPosts() { + this.loading = true + this.error = null + this.posts = [] + this.cdr.detectChanges() + + try { + const result = await this.query(this.GET_DATA, { + limit: 10, + }) + this.posts = result.data.posts + this.loading = false + this.cdr.detectChanges() + } catch (error) { + // Error is already handled in query method + this.loading = false + this.cdr.detectChanges() } + } - // ... (error handling and simulation methods omitted for brevity) - } + // ... (error handling and simulation methods omitted for brevity) +} ``` This Axios-powered component is revving up to fetch those posts faster than you can say "GraphQL"! Let's break down what's happening in this high-octane code: @@ -462,111 +493,125 @@ Here's the beauty of the Fetch API - there's nothing to install! 🎉 It's like #### 2. Code Snippets ```typescript - import { Component, ChangeDetectorRef } from '@angular/core'; - import { CommonModule } from '@angular/common'; - - @Component({ - selector: 'app-fetch-post-list', - standalone: true, - imports: [CommonModule], - template: ` -

Posts (Fetch Angular)

- - - - -
    -
  • {{ post.title }}
  • -
-
- {{ error }} -
- `, - // ... (styles omitted for brevity) - }) - export class FetchPostListComponent { - private endpoint = '/graphql'; - posts: any[] = []; - loading = false; - error: string | null = null; - - // Error simulation flags - private simulateNetworkError = false; - private simulateGraphQLError = false; - private simulateUnexpectedError = false; - - constructor(private cdr: ChangeDetectorRef) {} - - private GET_DATA = ` +import {Component, ChangeDetectorRef} from "@angular/core" +import {CommonModule} from "@angular/common" + +@Component({ + selector: "app-fetch-post-list", + standalone: true, + imports: [CommonModule], + template: ` +

Posts (Fetch Angular)

+ + + + +
    +
  • {{ post.title }}
  • +
+
+ {{ error }} +
+ `, + // ... (styles omitted for brevity) +}) +export class FetchPostListComponent { + private endpoint = "/graphql" + posts: any[] = [] + loading = false + error: string | null = null + + // Error simulation flags + private simulateNetworkError = false + private simulateGraphQLError = false + private simulateUnexpectedError = false + + constructor(private cdr: ChangeDetectorRef) {} + + private GET_DATA = ` query GetPosts($limit: Int) { posts(limit: $limit) { id title - ${this.simulateGraphQLError ? 'nonExistentField' : ''} + ${this.simulateGraphQLError ? "nonExistentField" : ""} } } - `; + ` - async query(queryString: string, variables: any = {}) { - if (this.simulateNetworkError) { - throw new Error('Simulated network error'); - } + async query(queryString: string, variables: any = {}) { + if (this.simulateNetworkError) { + throw new Error("Simulated network error") + } - if (this.simulateUnexpectedError) { - throw new Error('Simulated unexpected error'); - } + if (this.simulateUnexpectedError) { + throw new Error("Simulated unexpected error") + } - try { - const response = await fetch(this.endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - query: queryString, - variables, - }), - }); + try { + const response = await fetch(this.endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: queryString, + variables, + }), + }) - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + if (!response.ok) { + throw new Error( + `HTTP error! status: ${response.status}`, + ) + } - const result = await response.json(); + const result = await response.json() - if (result.errors) { - throw new Error(result.errors.map((e: any) => e.message).join(', ')); - } + if (result.errors) { + throw new Error( + result.errors + .map((e: any) => e.message) + .join(", "), + ) + } - return result; - } catch (error) { - this.handleError(error); - throw error; - } + return result + } catch (error) { + this.handleError(error) + throw error } - - async fetchPosts() { - this.loading = true; - this.error = null; - this.posts = []; - this.cdr.detectChanges(); - - try { - const result = await this.query(this.GET_DATA, { limit: 10 }); - this.posts = result.data.posts; - this.loading = false; - this.cdr.detectChanges(); - } catch (error) { - // Error is already handled in query method - this.loading = false; - this.cdr.detectChanges(); - } + } + + async fetchPosts() { + this.loading = true + this.error = null + this.posts = [] + this.cdr.detectChanges() + + try { + const result = await this.query(this.GET_DATA, { + limit: 10, + }) + this.posts = result.data.posts + this.loading = false + this.cdr.detectChanges() + } catch (error) { + // Error is already handled in query method + this.loading = false + this.cdr.detectChanges() } + } - // ... (error handling and simulation methods omitted for brevity) - } + // ... (error handling and simulation methods omitted for brevity) +} ``` This Fetch-powered component is leaner than a greyhound and faster than a caffeinated cheetah! Let's break down what's happening in this high-speed code: @@ -608,16 +653,15 @@ And there you have it, The Fetch API - the nimble, lightweight motorcycle of HTT Fetch shines when you need a lightweight, no-dependency solution that can handle both REST and GraphQL APIs. It's like having a motorcycle that's equally at home zipping through city traffic or cruising on the open highway. Plus, if you're looking to keep your project dependencies to a minimum, Fetch is your go-to ride. - ### 4. GraphQL Request - The Precision-Engineered Sports Car If Apollo was our luxury sedan, Axios our muscle car, and Fetch our nimble motorcycle, then GraphQL Request is like a finely-tuned sports car. It's designed specifically for GraphQL, offering a perfect balance of simplicity and power. Let's see how this beauty handles our data-fetching curves! 1. **Installation and Integration Steps** -Before we hit the track, let's get our GraphQL Request engine installed: + Before we hit the track, let's get our GraphQL Request engine installed: ```bash - npm install graphql-request graphql +npm install graphql-request graphql ``` No special configuration needed in your app.config.ts. Just import it in your component, and you're ready to race! @@ -627,86 +671,101 @@ No special configuration needed in your app.config.ts. Just import it in your co Now, let's pop the hood and examine our GraphQL Request-powered component: ```typescript - import { Component, ChangeDetectorRef } from '@angular/core'; - import { CommonModule } from '@angular/common'; - import { GraphQLClient, gql, ClientError } from 'graphql-request'; - - @Component({ - selector: 'app-graphql-request-post-list', - standalone: true, - imports: [CommonModule], - template: ` -

Posts (Graphql Request Angular)

- - - - -
    -
  • {{ post.title }}
  • -
-
- {{ error }} -
- `, - // ... (styles omitted for brevity) - }) - export class GraphqlRequestPostListComponent { - private client: GraphQLClient; - posts: any[] = []; - loading = false; - error: string | null = null; - - // Error simulation flags - private simulateNetworkError = false; - private simulateGraphQLError = false; - private simulateUnexpectedError = false; - - constructor(private cdr: ChangeDetectorRef) { - this.client = new GraphQLClient('http://localhost:4200/graphql'); - } - - private GET_DATA = gql` +import {Component, ChangeDetectorRef} from "@angular/core" +import {CommonModule} from "@angular/common" +import { + GraphQLClient, + gql, + ClientError, +} from "graphql-request" + +@Component({ + selector: "app-graphql-request-post-list", + standalone: true, + imports: [CommonModule], + template: ` +

Posts (Graphql Request Angular)

+ + + + +
    +
  • {{ post.title }}
  • +
+
+ {{ error }} +
+ `, + // ... (styles omitted for brevity) +}) +export class GraphqlRequestPostListComponent { + private client: GraphQLClient + posts: any[] = [] + loading = false + error: string | null = null + + // Error simulation flags + private simulateNetworkError = false + private simulateGraphQLError = false + private simulateUnexpectedError = false + + constructor(private cdr: ChangeDetectorRef) { + this.client = new GraphQLClient( + "http://localhost:4200/graphql", + ) + } + + private GET_DATA = gql` query GetPosts($limit: Int) { posts(limit: $limit) { id title - ${this.simulateGraphQLError ? 'nonExistentField' : ''} + ${this.simulateGraphQLError ? "nonExistentField" : ""} } } - `; + ` - async fetchPosts() { - this.loading = true; - this.error = null; - this.posts = []; - this.cdr.detectChanges(); + async fetchPosts() { + this.loading = true + this.error = null + this.posts = [] + this.cdr.detectChanges() - try { - if (this.simulateNetworkError) { - throw new Error('Simulated network error'); - } + try { + if (this.simulateNetworkError) { + throw new Error("Simulated network error") + } - if (this.simulateUnexpectedError) { - throw new Error('Simulated unexpected error'); - } + if (this.simulateUnexpectedError) { + throw new Error("Simulated unexpected error") + } - const result: any = await this.client.request(this.GET_DATA, { - limit: 10, - }); - this.posts = result.posts; - this.loading = false; - this.cdr.detectChanges(); - } catch (error) { - this.handleError(error); - this.loading = false; - this.cdr.detectChanges(); - } + const result: any = await this.client.request( + this.GET_DATA, + { + limit: 10, + }, + ) + this.posts = result.posts + this.loading = false + this.cdr.detectChanges() + } catch (error) { + this.handleError(error) + this.loading = false + this.cdr.detectChanges() } + } - // ... (error handling and simulation methods omitted for brevity) - } + // ... (error handling and simulation methods omitted for brevity) +} ``` Let's break down what's happening in this high-performance code: @@ -746,7 +805,6 @@ Now, let's talk about the advanced traction control system of our GraphQL Reques } ``` - This error handler is like having the world's best traction control and stability management system. Whether you hit a patch of black ice (network error), take a corner too fast (GraphQL error), or encounter an unexpected obstacle (other errors), it's got you covered with user-friendly messages. It even distinguishes between different types of errors, giving you precise control over how to handle each situation. #### Wrapping Up GraphQL Request @@ -761,8 +819,9 @@ GraphQL Request shines when you need a lightweight, GraphQL-specific solution th First things first, let's get our hands dirty with some installation magic. To bring Urql into your Angular project, you'll need to wave your command line wand and chant: ```bash - npm install @urql/core graphql +npm install @urql/core graphql ``` + We need to set up our Urql client. #### Code Snippets and Explanation @@ -770,27 +829,33 @@ We need to set up our Urql client. Let's break down our UrqlPostListComponent which you'll create following the same format above and solder structure: ```typescript - import { createClient, fetchExchange, cacheExchange, Client } from '@urql/core'; - - // ... other imports - - export class UrqlPostListComponent { - client: Client; - - constructor(private cdr: ChangeDetectorRef) { - this.client = createClient({ - url: 'http://localhost:4200/graphql', - exchanges: [cacheExchange, fetchExchange], - }); - } +import { + createClient, + fetchExchange, + cacheExchange, + Client, +} from "@urql/core" + +// ... other imports + +export class UrqlPostListComponent { + client: Client + + constructor(private cdr: ChangeDetectorRef) { + this.client = createClient({ + url: "http://localhost:4200/graphql", + exchanges: [cacheExchange, fetchExchange], + }) + } - // ... rest of the component - } + // ... rest of the component +} ``` Here, we're setting up our Urql client faster than you can say "GraphQL". We're telling it where to find our GraphQL endpoint and which exchanges to use. Think of exchanges as middleware for your GraphQL requests - they're like bouncers at a club, deciding how to handle incoming and outgoing traffic. Now, let's look at how we're fetching posts: + ```typescript getPostsQuery = gql` query GetPosts($limit: Int) { @@ -830,6 +895,7 @@ Now, let's look at how we're fetching posts: This fetchPosts method is where the magic happens. We're using Urql's query method to fetch our posts, handling the result like a pro juggler. If there's an error, we toss it to our error handler. If it's successful, we update our posts faster than you can say "data fetched"! #### Error Handling + Now, let's talk about error handling. In the world of APIs, errors are like unexpected plot twists in a movie - they keep things interesting, but you need to know how to handle them: ```typescript @@ -873,18 +939,17 @@ Remember, in the world of GraphQL clients, there's no one-size-fits-all solution 4. **Axios**: A promise-based HTTP client that can be used to send GraphQL queries. 5. **Fetch API**: The native browser API for making HTTP requests, used here for GraphQL queries. - ## Detailed Comparison Table -| Method | Bundle Size (minified + gzip)* | Learning Curve | Caching Capabilities | Community Support | Additional Features | -|------------------|---------------------------------|----------------|----------------------------------------|-------------------|----------------------------------------| -| Apollo Angular | ~2kB | Moderate | Extensive (InMemoryCache, customizable) | High | State management, optimistic UI updates | -| Urql | ~10.2 KB | Low | Moderate (Document caching) | Moderate | Extensible architecture, lightweight | -| GraphQL-Request | Unknown | Low | None (Minimal client) | Moderate | Simplicity, works in Node and browsers | -| Axios | ~13.2 KB | Low | None (HTTP client only) | High | Familiar HTTP handling, interceptors | -| Fetch API | 0 KB (Browser built-in) | Low | None (Native API) | High | No additional dependency, widely supported | +| Method | Bundle Size (minified + gzip)\* | Learning Curve | Caching Capabilities | Community Support | Additional Features | +| --------------- | ------------------------------- | -------------- | --------------------------------------- | ----------------- | ------------------------------------------ | +| Apollo Angular | ~2kB | Moderate | Extensive (InMemoryCache, customizable) | High | State management, optimistic UI updates | +| Urql | ~10.2 KB | Low | Moderate (Document caching) | Moderate | Extensible architecture, lightweight | +| GraphQL-Request | Unknown | Low | None (Minimal client) | Moderate | Simplicity, works in Node and browsers | +| Axios | ~13.2 KB | Low | None (HTTP client only) | High | Familiar HTTP handling, interceptors | +| Fetch API | 0 KB (Browser built-in) | Low | None (Native API) | High | No additional dependency, widely supported | -(*) Bundle sizes are approximate and may vary based on version and configuration. Values are culled from bundlephobia.com where available. +(\*) Bundle sizes are approximate and may vary based on version and configuration. Values are culled from bundlephobia.com where available. ### Notes: @@ -899,6 +964,7 @@ This table should help developers choose the right method based on their specifi ### Caching Capabilities 1. **Apollo Angular** + - Extensive caching capabilities through InMemoryCache - Normalization of data for efficient storage and retrieval - Customizable cache policies (cache-first, network-only, etc.) @@ -907,6 +973,7 @@ This table should help developers choose the right method based on their specifi - Ability to manually update and read from the cache 2. **Urql** + - Document caching by default - Customizable caching through exchangeable cache implementations - Supports normalized caching with additional setup @@ -914,11 +981,13 @@ This table should help developers choose the right method based on their specifi - Simpler caching model compared to Apollo, focusing on ease of use 3. **GraphQL-Request** + - No built-in caching mechanism - Requires manual implementation of caching if needed - Can be combined with external caching solutions or state management libraries 4. **Axios** + - No built-in GraphQL-specific caching - Can implement HTTP-level caching (e.g., using headers) - Requires manual implementation of application-level caching @@ -934,19 +1003,20 @@ In summary, Apollo Angular offers the most robust out-of-the-box caching solutio When choosing an approach, consider your application's complexity, performance requirements, and willingness to manage caching manually versus leveraging built-in solutions. - ## Common Issues and Resolutions 1. **Apollo Angular** Issue: Cache inconsistencies after mutations - Resolution: + Resolution: + - Ensure proper cache updates in mutation's `update` function - Use `refetchQueries` option to refresh related queries - Implement `optimisticResponse` for immediate UI updates Issue: Over-fetching data Resolution: + - Utilize fragments for reusable field selections - Implement proper query splitting for components - Use `@connection` directive for pagination to avoid refetching all data @@ -955,12 +1025,14 @@ When choosing an approach, consider your application's complexity, performance r Issue: Stale data after mutations Resolution: + - Use the `cache-and-network` request policy - Implement cache updates in mutation's `updates` option - Utilize the `refocusExchange` for automatic refetching on window focus Issue: Complex state management Resolution: + - Combine Urql with external state management libraries like NgRx if needed - Leverage Urql's `useQuery` and `useMutation` hooks for simpler state handling @@ -968,12 +1040,14 @@ When choosing an approach, consider your application's complexity, performance r Issue: Lack of automatic caching Resolution: + - Implement manual caching using services or state management libraries - Use HTTP caching headers for basic caching needs - Consider switching to Apollo or Urql for more complex applications Issue: Error handling complexities Resolution: + - Implement a centralized error handling service - Use TypeScript for better type checking and error prevention - Wrap GraphQL-Request calls in try-catch blocks for granular error handling @@ -982,12 +1056,14 @@ When choosing an approach, consider your application's complexity, performance r Issue: Constructing complex GraphQL queries Resolution: + - Use template literals for dynamic query construction - Implement a query builder utility for complex queries - Consider using a GraphQL-specific library for very complex schemas Issue: Handling GraphQL errors Resolution: + - Check for `errors` array in the response body - Implement custom error classes for different GraphQL error types - Use interceptors for global error handling @@ -996,12 +1072,14 @@ When choosing an approach, consider your application's complexity, performance r Issue: Verbose syntax for GraphQL operations Resolution: + - Create utility functions to abstract common GraphQL operations - Use TypeScript interfaces for better type safety and autocompletion - Consider using a lightweight wrapper around Fetch for GraphQL specifics Issue: Limited built-in features Resolution: + - Implement custom middleware for features like retries and caching - Use external libraries for advanced features (e.g., Observable support) - Create a custom Angular service to encapsulate Fetch API logic @@ -1042,4 +1120,3 @@ Remember, there's no one-size-fits-all solution. The best approach is the one th Whichever path you choose, GraphQL's power in providing flexible, efficient data fetching can significantly enhance your Angular applications. By understanding these different approaches, you're now equipped to make an informed decision and leverage GraphQL to its full potential in your Angular projects. Happy coding, and may your GraphQL queries be ever efficient! - From e351d93ecbf58d3aad53bd72c3eb015c21d44404 Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Sun, 14 Jul 2024 01:04:43 +0100 Subject: [PATCH 4/6] chore: sidebar item addition --- graphql/sidebar.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/graphql/sidebar.ts b/graphql/sidebar.ts index a9e8e752ff..beff98381a 100644 --- a/graphql/sidebar.ts +++ b/graphql/sidebar.ts @@ -5,7 +5,14 @@ const sidebars: SidebarsConfig = { { type: "category", label: "Guides", - items: ["graphql", "graphql-vs-rest", "graphql-react-client", "cto-guide", "problem-statement"], + items: [ + "graphql", + "graphql-vs-rest", + "graphql-react-client", + "graphql-angular-clients", + "cto-guide", + "problem-statement", + ], }, { type: "category", From 04f87f671152cc81c81743c263045d54d0e16edb Mon Sep 17 00:00:00 2001 From: amit Date: Sat, 20 Jul 2024 16:09:37 +0530 Subject: [PATCH 5/6] fix: move to blog add author info and cover image --- .../graphql-angular-clients-2024-07-20.md | 84 +++++++++++------- graphql/sidebar.ts | 9 +- src/css/custom.css | 15 ++-- static/images/blog/angular-with-graphql.png | Bin 0 -> 73067 bytes 4 files changed, 60 insertions(+), 48 deletions(-) rename graphql/graphql-angular-clients.md => blog/graphql-angular-clients-2024-07-20.md (95%) create mode 100644 static/images/blog/angular-with-graphql.png diff --git a/graphql/graphql-angular-clients.md b/blog/graphql-angular-clients-2024-07-20.md similarity index 95% rename from graphql/graphql-angular-clients.md rename to blog/graphql-angular-clients-2024-07-20.md index 1b225fda30..cf19b4d3f7 100644 --- a/graphql/graphql-angular-clients.md +++ b/blog/graphql-angular-clients-2024-07-20.md @@ -1,14 +1,23 @@ --- -title: "GraphQL in Angular: 5 Best Approaches for Data Fetching" -description: "Explore Apollo Angular, Urql, and other methods for efficient GraphQL integration in Angular applications, with detailed comparisons and error handling strategies." +authors: + - name: David Onyedikachi + title: NodeJs-Golang Backend Developer, with experience in Python, Rust, and Solidity + url: https://github.com/onyedikachi-david + image_url: https://avatars.githubusercontent.com/u/51977119?v=4 +tags: [GraphQL, Angular, Apollo client] +hide_table_of_contents: true +title: "Apollo vs Urql vs Fetch: The Ultimate Showdown" +description: "We pushed each method to its limits. Here's what we discovered." sidebar_label: "GraphQL with Angular" slug: graphql-angular-client --- -## Introduction +![Cover Image for Angular with GraphQL](../static/images/blog/angular-with-graphql.png) Angular developers often face the challenge of efficiently fetching and managing data from GraphQL APIs. This comprehensive guide dives into five powerful approaches for integrating GraphQL into your Angular applications. We'll explore everything from full-featured client libraries to lightweight solutions, using a practical example of fetching post data to demonstrate each method's strengths and nuances. + + Our journey will take us through Apollo Angular, Urql, GraphQL-Request, Axios, and the native Fetch API, each offering unique advantages for different project needs. Whether you're building a small-scale application or a complex enterprise system, this guide will equip you with the knowledge to choose the best GraphQL integration method for your Angular project. We'll not only cover the implementation details but also delve into error handling strategies, providing you with robust solutions to gracefully manage various API-related issues. By the end of this guide, you'll have a clear understanding of how to leverage GraphQL in Angular, complete with code snippets, real-world analogies, and a detailed comparison table to aid your decision-making process. @@ -18,38 +27,38 @@ So, buckle up and get ready to supercharge your Angular applications with the po _**NB**: We are not using the traditional NgModule-based Angular applications instead we will be using the newer standalone component approach; below is the version of angular cli version used throughout the guide._ ```shell - ng version +ng version _ _ ____ _ ___ - / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| - / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | - / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | - /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| - |___/ - - - Angular CLI: 18.0.7 - Node: 20.12.2 - Package Manager: npm 10.5.0 - OS: linux x64 - - Angular: 18.0.6 - ... animations, common, compiler, compiler-cli, core, forms - ... platform-browser, platform-browser-dynamic, platform-server - ... router - - Package Version - --------------------------------------------------------- - @angular-devkit/architect 0.1800.7 - @angular-devkit/build-angular 18.0.7 - @angular-devkit/core 18.0.7 - @angular-devkit/schematics 18.0.7 - @angular/cli 18.0.7 - @angular/ssr 18.0.7 - @schematics/angular 18.0.7 - rxjs 7.8.1 - typescript 5.4.5 - zone.js 0.14.7 + / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| + / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | + / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | +/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| + + + +Angular CLI: 18.0.7 +Node: 20.12.2 +Package Manager: npm 10.5.0 +OS: linux x64 + +Angular: 18.0.6 +... animations, common, compiler, compiler-cli, core, forms +... platform-browser, platform-browser-dynamic, platform-server +... router + +Package Version +--------------------------------------------------------- +@angular-devkit/architect 0.1800.7 +@angular-devkit/build-angular 18.0.7 +@angular-devkit/core 18.0.7 +@angular-devkit/schematics 18.0.7 +@angular/cli 18.0.7 +@angular/ssr 18.0.7 +@schematics/angular 18.0.7 +rxjs 7.8.1 +typescript 5.4.5 +zone.js 0.14.7 ``` We'll be using a Tailcall backend that wraps the JSONPlaceholder API, providing a GraphQL interface to RESTful data. @@ -927,6 +936,15 @@ You might be wondering, "Why should I choose Urql over other options?" Well, let 2. **Flexible**: It's adaptable to various use cases, like a chameleon in the coding world. 3. **Great Developer Experience**: With Urql, you'll feel like you're coding with a tailwind, not against a headwind. +:::tip +Want to see all this in action? +Check out our GitHub repo! + +We've put together a complete set of working examples for everything we've covered in this article. It's the perfect companion to help you dive deeper into Angular and GraphQL. + +[Explore the code on GitHub](https://github.com/onyedikachi-david/angular-graphql-multiapproach) +::: + #### Real-world Analogy If Apollo Client is like a fully-equipped Swiss Army knife, Urql is more like a sleek, modern multi-tool. It's streamlined, efficient, and gets the job done without unnecessary complexity. You know, like that one friend who always seems to have exactly what you need, no more, no less. diff --git a/graphql/sidebar.ts b/graphql/sidebar.ts index beff98381a..a9e8e752ff 100644 --- a/graphql/sidebar.ts +++ b/graphql/sidebar.ts @@ -5,14 +5,7 @@ const sidebars: SidebarsConfig = { { type: "category", label: "Guides", - items: [ - "graphql", - "graphql-vs-rest", - "graphql-react-client", - "graphql-angular-clients", - "cto-guide", - "problem-statement", - ], + items: ["graphql", "graphql-vs-rest", "graphql-react-client", "cto-guide", "problem-statement"], }, { type: "category", diff --git a/src/css/custom.css b/src/css/custom.css index 502b1472b6..b16819c810 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -565,13 +565,14 @@ span.token-line.theme-code-block-highlighted-line { display: none !important; } -.theme-code-block, pre.prism-code { - border-top-right-radius: 0!important; - border-top-left-radius: 0!important; - border-bottom-right-radius: 1.5rem!important; - border-bottom-left-radius: 1.5rem!important; +.theme-code-block, +pre.prism-code { + border-top-right-radius: 0 !important; + border-top-left-radius: 0 !important; + border-bottom-right-radius: 1.5rem !important; + border-bottom-left-radius: 1.5rem !important; } -[class*='codeBlockTitle'] { +[class*="codeBlockTitle"] { display: none; -} \ No newline at end of file +} diff --git a/static/images/blog/angular-with-graphql.png b/static/images/blog/angular-with-graphql.png new file mode 100644 index 0000000000000000000000000000000000000000..d1ee4417ed6a935f949959a15bafb1dfd13c25a4 GIT binary patch literal 73067 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

?z$GmHu-B?0vRHIT1Xj2rW&x?69N#T~q#IXUcYL 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 Date: Sat, 20 Jul 2024 16:20:36 +0530 Subject: [PATCH 6/6] fix:note --- blog/graphql-angular-clients-2024-07-20.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blog/graphql-angular-clients-2024-07-20.md b/blog/graphql-angular-clients-2024-07-20.md index ef55326921..b6a1ddec3c 100644 --- a/blog/graphql-angular-clients-2024-07-20.md +++ b/blog/graphql-angular-clients-2024-07-20.md @@ -24,7 +24,9 @@ We'll not only cover the implementation details but also delve into error handli So, buckle up and get ready to supercharge your Angular applications with the power of GraphQL! +:::note _**NB**: We are not using the traditional NgModule-based Angular applications instead we will be using the newer standalone component approach; below is the version of angular cli version used throughout the guide._ +::: ```shell ng version