I have talked about the server-side implementation of GraphQL in my earlier article and I have received many requests to write about the client-side implementation, which I have used Apollo Client in Angular 6.x application.
In this article, we will talk about Apollo Client for Angular, its dependencies and what packages I have used to in my application to build my Angular 6.x Application.
As per their website,
Apollo Client is the ultra-flexible, community-driven GraphQL client for Angular, JavaScript, and native platforms. It is designed from the ground up to make it easy to build UI components that fetch data with GraphQL.
Which simply means that using Apollo Client will help you set up everything you need to connect to your GraphQL server with bare minimum code without worrying too much of GraphQL implementation on the client. Apollo Client supports the most popular frameworks and libraries like Angular, React, Vue, Meteor, Ember and Polymer.
To get started with Apollo Client for Angular, you need to install below npm packages
A full-fledged Apollo Client package implemented for the applications built in Angular framework.
A network interface for Apollo Client
Easily set a context on your operation, which is used by other links further down the chain.
Handle and inspect errors in your GraphQL network stack.
Get GraphQL results over a network using HTTP fetch.
Send GraphQL operations over a WebSocket. Works with GraphQL Subscriptions.
Helpful utilities for parsing GraphQL queries
Setting up the Apollo client in Angular application is very simple and easy, all you have to do is add Apollo Angular Client in your src/app/app.module.ts
file.
In the app.module.ts
file, you can create a GraphQL HttpLink to access GraphQL over HTTP
const http = new HttpLink({
uri: `http://your_graphql_endpoint`
});
The WebSocketLink in Apollo lets you connect with your GraphQL server using Web Sockets.
const ws = new WebSocketLink({
uri: `ws://your_graphql_endpoint`,
options: {
reconnect: true
}
});
The context in Apollo Client lets you manage request headers and set the authorization token in every request made by Apollo Client to the GraphQL server.
const authContext = setContext(async (request, previousContext) => {
// get the token from session or storage
const token = await this.session.getToken();
if(!token) {
return {}
}
// Set the Authorization headers
return {
headers: {Authorization: `Bearer ${token}`}
}
});
Apollo Client comes with prebuilt Error and Exception Handler which can catch all the Errors and Expectations made my GraphQL server.
const error = onError(({graphQLErrors, networkError, response, operation}) => {
// Checking GraphQL Errors
if(graphQLErrors) {
graphQLErrors.map(({message, path, type}) => {
if(type === 'INVALID_TOKEN') {
this.session.destroy();
}
if(type === 'UNAUTHENTICATED') {
this.session.destroy();
}
console.log(`[GraphQL Error]: Type: ${type}, Message: ${message}`, path);
});
}
// Checking in Network Errors
if(networkError) {
console.log(`[Network Error]:`, networkError);
}
});
Apollo Links also allows capturing all the responses from GraphQL server to one place, which can be helpful to set tokens and other required variables on the client side.
const afterwareLink = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
const {
response: { headers }
} = operation.getContext();
if(headers) {
const _token = headers.get('Authorization');
if(_token && _token !== 'null') {
const payload = getPayload(_token);
this.session.setToken(_token);
}
}
return response;
});
});
We need to split the ApolloLink for both the transport HTTP and WebSocket and using the split
method from apollo-link
package.
const link = split(({query}) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
}, ws, ApolloLink.from([authContext, error, afterwareLink, http]));
We have created separate links and implemented various functions for these links such as handling all the requests being sent by ApolloClient with its headers, Error Messages thrown by the ApolloServer, Capturing all the responses received by ApolloClient with its headers and implement multichannel Link to the GraphQL sever on HTTP and WebSocket.
Not its time to join these pieces together and create the final ApolloLink using create
method.
apollo.create({
link: link,
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: 'network-only'
}
}
});
Putting everything into theapp.module.ts
file.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
import { ApolloModule, Apollo } from 'apollo-angular';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { setContext } from 'apollo-link-context';
import { onError } from 'apollo-link-error';
import { ApolloLink, split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { SessionService } from './common/session.service';
@NgModule({
declarations: [
...
],
imports: [
...
ApolloModule,
...
],
providers: [
...
],
bootstrap: [AppComponent]
})
export class AppModule{
constructor(
private apollo: Apollo,
private session: SessionService
) {
const protocol = environment.https ? 'https' : 'http';
// Creating new HttpLink for Apollo Client
const http = new HttpLink({
uri: `${protocol}://${environment.graphqlUrl}`,
});
// Creating new WebSocketLink for ApolloClient to support GraphQL subscriptions
const ws = new WebSocketLink({
uri: `ws:${environment.graphqlUrl}`,
options: {
reconnect: true
}
});
// authContext to set Authorization token for every request sent from client
const authContext = setContext(async (request, previousContext) => {
// Getting the token from the session service
const token = await this.session.getToken();
// return {} if token is not set yet
if(!token) {
return {}
}
// Set Authorization headers with token
return {
headers: {Authorization: `Bearer ${token}`}
}
});
// Error handling for GraphQL client
const error = onError(({graphQLErrors, networkError, response, operation}) => {
if(graphQLErrors) {
graphQLErrors.map(({message, path, type}) => {
// Destroy the session if INVALID_TOKEN receieved from the server
// Forcing user to login again
if(type === 'INVALID_TOKEN') {
this.session.destroy();
}
// Forcing user to login again if token is UNAUTHENTICATED
if(type === 'UNAUTHENTICATED') {
this.session.destroy();
}
// Loggin the GraphQL errors on the console
console.log(`[GraphQL Error]: Type: ${type}, Message: ${message}`, path);
});
}
// Login the Network errors
if(networkError) {
console.log(`[Network Error]:`, networkError);
}
});
// afterwareLink, Apollo link
const afterwareLink = new ApolloLink((operation, forward) => {
// tap to the forward reuqest operation
return forward(operation).map(response => {
// get the response headers
const {
response: {headers}
} = operation.getContext();
// check if headers recieved
if(headers) {
// get the token from the response header
const _token = headers.get('Authorization');
// check if token is not null
if(_token && _token !== 'null') {
// get playload for of the token
const payload = getPayload(_token);
// set the new token into the session
this.session.setToken(_token);
}
}
return response;
});
});
// creating the conditional link for http and ws requests
const link = split(({query}) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
}, ws, ApolloLink.from([authContext, error, afterwareLink, http]));
// creating the final Apollo client link with all the parameters
apollo.create({
link: link,
cache: new InMemoryCache(),
defaultOptions: {
query: {
fetchPolicy: 'network-only'
}
}
});
}
}
The above code is having all the required implementation for your application using Apollo Angular Client for a RESTful based web application which sends and received JWT tokens in every request.
A GraphQL Fragment is a shared piece of Query logic and has two main principles
To define GraphQL Fragments I have created a new file src/app/fragments.ts
// src/app/fragments.ts
import gql from 'graphql-tag';
// A GraphQL Fragment on User type
export const userFields = gql`
fragment userFields on User {
first_name
last_name
email
password
bio
}
`;
export const postFields = gql`
fragment postFields on Post {
author {
..userFeilds
}
title
description
banner
}
${userFields}
`;
I have created a new file src/app/graphql.ts
to define all the GraphQL Queries, Mutations and Subscriptions
// src/app/graphql.ts
import gql from 'graphql-tag';
import { userFields, postFields } from './fragments';
export const QUERY_USERS = gql`
query users {
users {
...userFields
}
}
${userFields}
`;
export const MUTATION_LOGIN = gql`
mutation login($email: String!, $password: String!){
login(email: $email, password: $password){
...userFields
}
}
${userFields}
`;
export const SUBSCRIPTION_POST = gql`
subscription post {
post {
...postFields
}
}
${postFields}
`;
In Apollo client making a query to GraphQL endpoint is easy and simple, I have used Apollo from apollo-angular as a dependency in my services and used the same to make query, mutation and subscription requests
import { Injectable } from '@angular/core';
import { Apollo } from 'apollo-angular';
import {
QUERY_USERS,
MUTATION_LOGIN,
SUBSCRIPTION_POST
} from './graphql';
@Injectable({
providedIn: 'root'
})
export class AppService {
constructor(
private apollo: Apollo,
) { }
login(email: string, password: string) {
return this.apollo.mutate({
mutation: MUTATION_LOGIN,
variables: {
email,
password
}
}).toPromise(); // returning as promise
}
users() {
return this.apollo.query({
query: QUERY_USERS
}).toPromise();
}
posts() {
return this.apollo.subscribe({
query: SUBSCRIPTION_POST
}); // return subscription
}
}
Apollo Makes using GraphQL simple and easy and its backed by a huge community which helping people like us to adapt and start using GraphQL for our applications.
Let me know your implementation of GraphQL on client-side, I would love to learn more about the same.
Share with your developer friends and help the community.
Google Develover Expert — WebTechnologies and Angular | Principal Architect at Naukri.com | Entrepreneur | TechEnthusiast | Speaker