Using GraphQL with Apollo Angular

GraphQL has been on the market for more than five years, yet there is still some confusion about its use. Get an understanding of GraphQL and check out our tips and best practices on getting started with one of the most popular GraphQL clients for Angular, Apollo Angular.

In short, GraphQL is a query language for APIs and a runtime for fulfilling those queries with existing data. One of its best features is that it gives clients the power to ask for the exact data they need in a single request. Comparing GraphQL vs. REST, we can say that the client retrieves data from multiple URLs and extracts what’s needed with typical REST APIs. On the other hand, with GraphQL APIs, the client always gets exactly what is required - nothing more and nothing less.

Check it out in the Apollo GraphQL examples below, but before here’s a short breakdown of its main advantages:

  1. Get the exact (and only) data you need in a single request.
  2. Working very fast as it is not returning the data you didn’t ask for.
  3. Developer-friendly, easy to construct queries for any request.
  4. Good for microservices.
  5. Requiring less code from frontend developers, since you can make a query to get everything at once.
  6. Fast development speed.
  7. Growing documentation on its use.

How to Set-Up Apollo GraphQL?

There are many GraphQL client libraries available. However, the most popular one is the Apollo Client, probably because it offers:

  • interoperability with other frameworks
  • modern data handling with declarative data fetching
  • modern state management with Apollo Link

The setup for Angular is quite simple:

1. Installation

npm i apollo-angular @apollo/client graphql@^15

@apolo/client is encapsulated package where it has already implemented how the client should connect to the graphql server.

Apollo-angular is the package that is actually connecting apollo/client and Angular.

2. Add Synciterable to tsconfig (It Is Required By @apolo/client)

lib: {
	"esnext.asynciterable"
}

3. Create GraphQL Module

import { HttpClientModule } from '@angular/common/http';
import { APOLLO_OPTIONS } from 'apollo-angular';
import { HttpLink } from 'apollo-angular/http';
import { InMemoryCache } from '@apollo/client/core';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { environment } from 'src/environments/environment';

export function createApolloFactory(httpLink: HttpLink) {
  return {
    link: httpLink.create({ uri: environment.graphqlURL }),  // HERE WE WILL PUT OUR GRAPHQL URL
    cache: new InMemoryCache(),
  };
}

@NgModule({
  imports: [BrowserModule, HttpClientModule],
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApolloFactory,
      deps: [HttpLink],
    },
  ],
})
export class GraphQLModule {}

Angular Architecture Best Practices

On one side, there is no perfect or right way to design your Angular application architecture. On the other, there are some best practices. It is smart to learn about it, so you’ll be able to decide on your best approach when designing Angular architecture.

Usually, the best way to start is by creating a folder and inside it a module for every feature. In this case, that would be “film.” Though, to keep things simple, we will not create a film module; therefore, we will include components directly in the app.module.

A recommended good practice has a GraphQL folder that contains all GraphQL related services, models and constants for every module:  

  • GraphQLService: a service that will encapsulate the GraphQL API calls.
  • GraphQL Models: the Typescript types related to the service.
  • GraphQL Constants: the Apollo queries used in the service.

GraphQL Single Module Implementation Example

Below, you can find the described approach where we implemented GraphQL for a single module. If multiple modules use the same queries, we restructure them to be shared.

GraphQL Structure in the Module

----- src 
------ app
------- film
-------- graphql
--------- film-graphql-constants.ts
--------- film-graphql-service.ts
--------- film-model.ts
-------- pages
--------- films
--------- starships
------- app-routing.module.ts
------- app.component.html
------- app.component.scss
------- appcomponent.spec.ts
------- app.component.ts
------- app.module.ts
------- graphql.module.ts

GraphQL Service

import { Injectable } from '@angular/core';
import { Apollo, Query, QueryRef } from 'apollo-angular';
import { map, Observable } from 'rxjs';
import { ALL_FILMS_QUERY, GET_FILM_BY_ID } from './film-graphql-constants';
import {
  GraphQLFilm,
  GraphQLResponseAllFilms,
  GraphQLResponseFilmById,
} from './film.model';

@Injectable({
  providedIn: 'root',
})
export class FilmGraphqlService {
  constructor(private apollo: Apollo) {}
  
  getAllFilms(): Observable<GraphQLFilm[]> {
    // We will include pagination here as well 
    return this.apollo
      .query<GraphQLResponseAllFilms>({ query: ALL_FILMS_QUERY })
      .pipe(map((m) => m.data.allFilms.films));
  }
  
  getFilmById(filmId: number): Observable<GraphQLFilm> {
    return this.apollo
      .query<GraphQLResponseFilmById, { filmId: number }>({
        query: GET_FILM_BY_ID,
        variables: {
          filmId,
        },
      })
      .pipe(map((m) => m.data.film));
  }
}

GraphQL Models

export interface GraphQLFilm {
  title: string;
  director: string;
  producers: string[];
  created: Date;
  episodeID: string;
  characterConnection: GraphQLResponseFilmByIdCharacterConn;
  starshipConnection: GraphQLResponseFilmByIdStarshipConn;
}
export interface GraphQLResponseAllFilms {
  allFilms: {
    films: GraphQLFilm[];
  };
}
export interface GraphQLResponseFilmById {
  film: GraphQLFilm;
}
export interface GraphQLResponseFilmByIdCharacterConn {
  characters: GraphQLCharacter[];
}
export interface GraphQLResponseFilmByIdStarshipConn {
  starships: GraphQLStarship[];
}
export interface GraphQLCharacter {
  gender: string;
  name: string;
}
export interface GraphQLStarship {
  model: string;
  name: string;
}

GraphQL Constants

import { gql } from 'apollo-angular';
export const ALL_FILMS_QUERY = gql`
  query {
    allFilms {
      films {
        title
        producers
        episodeID
        id
        director
      }
    }
  }
`;
export const GET_FILM_BY_ID = gql`
  query GetFilmById($filmId: ID!) {
    film(filmID: $filmId) {
      title
      director
      producers
      created
      starshipConnection {
        starships {
          name
          model
          manufacturers
        }
      }
      characterConnection {
        characters {
          name
          birthYear
          gender
        }
      }
    }
  }
`;

Query vs. watchQuery

In the example above, we have been using a query. However, in some applications, we need to have live data. In that case, we use watchQuery.

What is the difference? On one side, the query will get data once - you can think of it as a simple GET method. On the other, watchQuery will constantly track data for the given query. Whenever the data is changed, it will be triggered.

Query vs. Mutations

Mutations are identical to queries in syntax. The only difference is that you use the keyword mutation instead of the query to indicate that the operation is changing the dataset behind the schema. We can use queries for fetching data with or without params and mutations for state-changing methods like (PUT, DELETE, POST, etc.).

If you’ve reached the end of this post, you should now have some basic understanding of GraphQL, how it is used and what its benefits are. Next, consider trying out GraphQL by yourself as it has been steadily rising in popularity throughout the years.

Here you can find a free GraphQL film documentation where you can explore and play with  some other queries: