Skip to content

Connecting to PokeAPI with Angular and Apollo Client

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

GraphQL is becoming more relevant each passing day, and becoming necessary to connect to many APIs on the web. In this post, I'll show you how to connect to a publically available GraphQL API with Angular and Apollo Client. You will need basic knowledge of JavaScript to follow along, and although some basic Angular knowledge is useful, it is not entirely necessary, and I'll explain enough so that anyone can follow along.

GraphQL and Apollo

GraphQL is a query language that is used to interact with APIs on the web. It's possible you may have used RESTful APIs in the past, and GraphQL aims to alleviate some of the issues it has such as over-fetching, and extra round trips to the API. This is done by allowing queries to specify only the fields it needs, and by allowing batching of multiple queries in a single request.

Apollo on the other hand is a popular set of tools used to create and interact with GraphQL APIs. For this demonstration, we're only concerned with the client, which in this case is a web browser since we're using Angular. We'll be using the unofficial Apollo Angular library. This uses Apollo Client under the hood, and exposes several useful features such as fetching, caching, state management, and more.

Getting Started

We are going to use the command-line tool ng to manage our new Angular project. ng makes it easy to bootstrap new Angular projects, and will create all the boilerplate, and set us up with some default configurations that will allow us to build and serve our project. This tool can be installed globally with npm by running the following command:

npm install -g @angular/cli

Now navigate to your projects directory on your system, and run the following:

ng new angular-pokeapi-app

When prompted to add routing to the project, make sure to say "yes". For this demo, I chose basic CSS when prompted, though any of the CSS flavors offered will do. After you choose both of those options, ng should have hopefully setup our project's boilerplate.

You should be able to serve the website now with the following:

ng serve

If successful, you should see a message that tells you to navigate to http://localhost:4200/ in your browser. If you see the following page that means everything was successful!

Screenshot 2021-10-07 162831

PokeAPI

The PokeAPI is a publically available API that can be used to query information about Pokémon. This API is accessible both over REST and GraphQL. This API uses many features and design patterns with GraphQL that you will see in the real world. The nice thing about GraphQL is you can run queries directly from your browser. Load up the PokeAPI console and try executing the following query to get a list of gen 3 Pokémon:

query samplePokeAPIquery {
  gen3_species: pokemon_v2_pokemonspecies(where: {}, order_by: {id: asc}) {
    name
    id
  }
  generations: pokemon_v2_generation {
    name
    pokemon_species: pokemon_v2_pokemonspecies_aggregate {
      aggregate {
        count
      }
    }
  }
}

Now that we know the PokeAPI is working as expected, we can go back to our Angular application and start working on integrating with it!

Component Setup and Routing

Let's create a few components and setup routing between them. We'll want to make a page to list Pokémon from the PokeAPI, and a page that allows us to view individual Pokémon. For starters, I would recommend emptying out app.component.html as it contains the starter page. We'll want to render our list of Pokémon here later, and it will be easier if we start from a clean slate.

First, let's create a component that will render a list of Pokémon later:

ng generate component pokemon-list -m app

Add another component for rendering individual Pokémon:

ng generate component pokemon -m app

These commands should have both created our components and update declarations in app.module.ts so we can use them. Now that both those components are added we should be able to add routes for them by modifying the routes variable in app-routing.module.ts:

...

// Import our new components.
import { PokemonListComponent } from './pokemon-list/pokemon-list.component';
import { PokemonComponent } from './pokemon/pokemon.component';

const routes: Routes = [
  { path: '', redirectTo: '/pokemon', pathMatch: 'full' },
  { path: 'pokemon', component: PokemonListComponent },
  { path: 'pokemon/:id', component: PokemonComponent },
];

...

Here, I map /pokemon to the list and /pokemon/:id to the Pokémon detail component. I also add a redirect so the root page navigates the browser to /pokemon, though this is optional. If ng serve is still running, then you should be able to render both components by visiting their URLs. The Pokémon list component, for example, can be reached at http://localhost:4200/pokemon.

Screenshot 2021-10-10 170018

Installing Apollo Client

Apollo Client can be installed just as any other dependency, but there's also a bit of setup involved other than just importing it to get it working in your project. The best way to add Apollo Client to your project is with Angular's own built-in ng add subcommand. Run the following to add Apollo Client to your project:

ng add apollo-angular

You will be prompted for a GraphQL URL while adding the dependency. Input https://beta.pokeapi.co/graphql/v1beta as the URL, which is the GraphQL endpoint we'll be making POST requests to later. Once you do that, you should get an output that roughly looks like the following:

Screenshot 2021-10-08 103853

The results will inform you of which files have been created and changed. This is the benefit of using ng add over npm install when installing Apollo Client. ng add will automatically setup Apollo Client in our project for us in addition to installing it! The file we'll look at first is src/app/graphql.module.ts, which is a new file that was added after installation.

// ... imports

const uri = 'https://beta.pokeapi.co/graphql/v1beta'; // <-- add the URL of the GraphQL server here
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
  return {
    link: httpLink.create({ uri }),
    cache: new InMemoryCache(),
  };
}

@NgModule({
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApollo,
      deps: [HttpLink],
    },
  ],
})
export class GraphQLModule {}

This file exports a factory function, and an NG module that adds an Apollo Client provider. Note how the factory function takes a parameter with type HttpLink. This is a class provided by Apollo Client that depends on Angular's built-in HttpClient, which we will need to import as a module. Thankfully ng add is intelligent, and added a declaration for both HttpClientModule and Apollo Client's own GraphQLModule in the updated file app.module.ts.

// ... imports

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppRoutingModule, GraphQLModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Let's Fetch Some Data!

Now that all the visual components are in place and dependencies installed, we are now ready to start calling the PokeAPI. To start off, we're going to create a couple of services with our queries in them, and some typings for the data contained within. These serivces will contain classes that extend off apollo-graphql's Query<T> class.

schema.ts

export interface Species {
  name: string;
  id: number;
  is_legendary?: boolean;
  capture_rate?: number;
  gender_rate?: number;
  generation_id: number;
  evolves_from_species_id?: number;
  pokemon_v2_evolutionchain?: {
    pokemon_v2_pokemonspecies: Species[];
  };
  pokemon_v2_pokemonspeciesflavortexts: {
    flavor_text: string;
  }[];
}

get-pokemon-list.service.ts

import { Injectable } from '@angular/core';
import { gql, Query } from 'apollo-angular';
import { Species } from '../schema';

interface SpeciesListResponse {
  species: Species[];
}

@Injectable({
  providedIn: 'root',
})
export class GetPokemonListService extends Query<SpeciesListResponse> {
  document = gql`
    query pokemonList($limit: Int!, $offset: Int!) {
      species: pokemon_v2_pokemonspecies(
        order_by: { generation_id: asc, id: asc }
        limit: $limit
        offset: $offset
      ) {
        name
        id
        generation_id
      }
    }
  `;
}

get-pokemon.service.ts

import { Injectable } from '@angular/core';
import { gql, Query } from 'apollo-angular';
import { Species } from '../schema';

interface SpeciesResponse {
  species: Species;
}

@Injectable({
  providedIn: 'root',
})
export class GetPokemonService extends Query<SpeciesResponse> {
  document = gql`
    query pokemon($id: Int!) {
      species: pokemon_v2_pokemonspecies_by_pk(id: $id) {
        id
        name
        is_legendary
        capture_rate
        gender_rate
        generation_id
        evolves_from_species_id
        pokemon_v2_evolutionchain {
          pokemon_v2_pokemonspecies {
            id
            name
            generation_id
          }
        }
        pokemon_v2_pokemonspeciesflavortexts(
          where: { language_id: { _eq: 9 } }
          order_by: { version_id: desc }
          limit: 1
        ) {
          flavor_text
        }
      }
    }
  `;
}

The query that will be called resides in the query property of the class. You may notice that we define an interface in both services. This is done so we can properly type everything in our responses.

The code for the components is very simple at this point. We're going to create an observable for our pokemon and render them in the template. We can populate the observable by injecting our GetPokemonListService from before, and then watch() it.

pokemon-list.component.ts

@Component({
  selector: 'app-pokemon-list',
  templateUrl: './pokemon-list.component.html',
  styleUrls: ['./pokemon-list.component.css'],
})
export class PokemonListComponent implements OnInit {
  pokemon$!: Observable<Species[]>;

  constructor(private getPokemonList: GetPokemonListService) {}

  ngOnInit(): void {
    this.pokemon$ = this.getPokemonList
      .watch({ limit: 50, offset: 0 })
      .valueChanges.pipe(map((result) => result.data.species));
  }
}

In watch(), we can pass query parameters, and in this case, we just hard-coded some pagination parameters. These parameters can be adjusted to get different "pages" of data if we so desire. After 'watch' is valueChanges, which is an observable that will emit query results. There's a data envelope on the resulting object, and we just want a list of species, so we use RsJS to map the results to the array contained within.

Now the template can simply iterate over the observable using an async pipe:

pokemon-list.component.html

<h2>Pokémon</h2>

<ul>
  <li *ngFor="let pokemon of pokemon$ | async">
    <a routerLink="/pokemon/{{ pokemon.id }}">
      <span>{{ pokemon.id }}</span> {{ pokemon.name }}
    </a>
  </li>
</ul>

Then you should get something roughly like this:

Screenshot 2021-10-19 092321

The template contains anchors that should link to the other page. The component for this page is very similar to the one we just wrote, so I'll be brief.

pokemon.component.ts

@Component({
  selector: 'app-pokemon',
  templateUrl: './pokemon.component.html',
  styleUrls: ['./pokemon.component.css'],
})
export class PokemonComponent implements OnInit {
  pokemon$!: Observable<Species>;

  constructor(
    private route: ActivatedRoute,
    private getPokemon: GetPokemonService
  ) {}

  ngOnInit(): void {
    this.route.params.subscribe((routeParams) => {
      this.pokemon$ = this.getPokemon
        .watch({ id: routeParams.id })
        .valueChanges.pipe(map((result) => result.data.species));
    });
  }
}

The primary difference here is we pull in the ActivatedRoute service so we can read the Pokémon's ID in the URL parameters. The GetPokemonService service takes the ID returned from the router as a parameter. Things get a little more interesting with the template.

pokemon.component.html

<h2>{{ (pokemon$ | async)?.name | titlecase }}</h2>

<a routerLink="/pokemon">Return to Pokemon list</a>

<p
  *ngFor="
    let flavorText of (pokemon$ | async)?.pokemon_v2_pokemonspeciesflavortexts
  "
>
  {{ flavorText.flavor_text }}
</p>

<h3>Evolution Chain</h3>

<ul
  *ngFor="
    let pokemon of (pokemon$ | async)?.pokemon_v2_evolutionchain
      ?.pokemon_v2_pokemonspecies
  "
>
  <li>
    <a routerLink="/pokemon/{{ pokemon.id }}">{{ pokemon.name }}</a>
  </li>
</ul>

Here, we render a list of Pokémon in the same evolution chain with links to them, and some flavor text which should look something like this:

Screenshot 2021-10-19 093304

At this point, you should have a basic application that can browse Pokémon! There's a lot of improvement that can be made though such as adding pagination, and adding some styles for example, though I feel that's outside the scope of this article.

Conclusion

We have only scratched the surface of the Apollo Angular library and the PokeAPI itself for that matter. Apollo Angular provides a lot of functionality that makes it work well with Angular. I highly recommend checking out the following documentation links to learn more! Feel free to check out the code on GitHub as well.

Documentation

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co