An intro to GraphQL with Apollo Client and Angular

rajvirtual

Raj Vijay

Posted on December 19, 2019

An intro to GraphQL with Apollo Client and Angular

Recently I watched an egghead.io tutorial from Eve Porcello, demonstrating how to communicate with a GraphQL API using the GraphQL query language. The instructor uses a tool called GraphQL Playground(https://pet-library.moonhighway.com), which lets the user, send queries to GraphQL endpoints and allows the user to perform various tasks on a pet library like authentication, return a list of pets, check-in and check-out pets from the pet library e.t.c.

In this blog post,we will build a simple clone of pet library playground from scratch using Angular and Apollo, with features including authentication, returning a list of all pets and check-in/check-out pets.

You can view the full source code here.

Let's start by adding Apollo Angular by using this command.

ng add apollo-angular

The above command will add an NgModule(graphql.module.ts) with an initial setup to the project. To this we need to add the URL of our GraphQL server, which in our case is http://pet-library.moonhighway.com

Let's start with returning a list of all pets from the library. List screen should have a filter to see All, Available and Checked Out status. Our final UI should look like this.

Pets UI

Let's add a new file pet.ts under models folder. You should be able to view the data types under the Schema tab in http://pet-library.moonhighway.com.

enum PetCategory {
    CAT,
    DOG,
    RABBIT,
    STINGRAY
}

export enum PetStatus {
    AVAILABLE,
    CHECKEDOUT
}

export interface Pet {
    id: string;
    name: string;
    weight: number;
    category: PetCategory;
    status: PetStatus;
    dueDate?: Date;
}
Enter fullscreen mode Exit fullscreen mode

Query

We will create a file called pets-filter-query.ts under the queries folder and wrap the query with a gql function, like shown below. I am using a fragment called petFields in case if i need to reuse the fields in some other query. You can learn more about fragments here.

import gql from 'graphql-tag';
const petFieldsQuery = gql`
  fragment petFields on Pet {
    id
    name
    category
    status
    inCareOf{
      name
    }
  }`;

export const filterPetsQuery = gql`
query petsQuery($status : PetStatus)
{
  allPets(status : $status)  {
      ...petFields
  }
} ${petFieldsQuery}`;
```



## UI Layer

Let's then add a list component and update the html with the following.



```
<div class="row pt-4">
    <div class="col-sm-8">
        <span style="color: #007bff;font-weight :bold;">Pet Status: </span>
        <div ngbDropdown class="d-inline-block">
            <button class="btn btn-outline-primary" id="dropdownBasic1" ngbDropdownToggle>{{getStatusName()}}</button>
            <div ngbDropdownMenu aria-labelledby="dropdownBasic1">
                <button ngbDropdownItem *ngFor="let item of petStatus" (click)="changePetStatus(item)">
                    {{item.name}}</button>
            </div>
        </div>
        <div>
            <button class="btn btn-link float-sm-right" (click)=logOut()>Log Out</button>
        </div>
    </div>
</div>
<div class="row">
    <div class="col-sm-8">
        <table class="table table-striped">
            <thead>
                <tr>
                    <td class="w-25">
                        <p> Pet </p>
                    </td>
                    <td class="w-30">
                        <p> Category</p>
                    </td>
                    <td class="w-50">
                        <p> Customer</p>
                    </td>
                    <td class="w-50">
                        <p> Action</p>
                    </td>
                </tr>
            </thead>
            <tr *ngFor="let pet of pets$ | async |  select: 'allPets' ">
                <td>
                    {{pet.name}}
                </td>
                <td>
                    {{pet.category}}
                </td>
                <td>
                    {{pet.inCareOf?.name}}
                </td>
                <td>
                    <button (click)="checkInOut(pet)" *ngIf="loggedIn"
                        class="btn btn-link">{{pet.status == 'AVAILABLE' ? 'Check Out' : 'Check In' }}</button>
                </td>
            </tr>
        </table>
    </div>
</div>
```




## Service Layer

Next, add a pet.service.ts under services folder and update the code as shown below.



```javascript
  getPetsByStatus(petStatus: string) {
    return this.apollo
      .watchQuery({
        query: filterPetsQuery,
        fetchPolicy: 'network-only',
        variables: {
          status: petStatus,
        },
      })
      .valueChanges;
  }
```


I am adding 'network-only'as fetch-policy since for this call, I don't want the results from the cache and will always request data from the server. Apollo watchQuery returns an Observable to which we will subscribe in list.component.ts.

## Login
To perform Check In/Check Out we need to login first, as we have to send in a user token with every request. So let's work on our login component. In the command prompt enter

`ng g c login`

Then edit login.component.html to add this following markup.


```
<h2>Login</h2>
<form #loginForm="ngForm" (ngSubmit)="loginForm.form.valid && onSubmit()">
    <p style="color: red;font-weight: 600;">{{errorMessage}}</p>
    <div class="form-group">
        <label for="username">Username</label>
        <input type="text" name="username" [(ngModel)]="model.username" #username="ngModel" class="form-control"
            [ngClass]="{ 'is-invalid': loginForm.submitted && username.invalid }" required />
        <div *ngIf="loginForm.submitted && username.invalid" class="invalid-feedback">
            <div *ngIf="username.errors.required">User Name is required</div>
        </div>
    </div>
    <div class="form-group">
        <label for="password">Password</label>
        <input type="password" name="password" [(ngModel)]="model.password" #password="ngModel" class="form-control"
            [ngClass]="{ 'is-invalid': loginForm.submitted && password.invalid }" required />
        <div *ngIf="loginForm.submitted && password.invalid" class="invalid-feedback">
            <div *ngIf="password.errors.required">Password is required</div>
        </div>
    </div>
    <div class="form-group">
        <button [disabled]="loading" class="btn btn-primary">Login</button>
        <a routerLink="/register" class="btn btn-link">Register</a>
    </div>
</form>
```



Add a login mutation under queries folder as shown below. We will create a class called LoginQL which extends from a Mutation class from apollo-angular.



```javascript
import { Injectable } from '@angular/core';
import { Mutation } from 'apollo-angular';
import gql from 'graphql-tag';

@Injectable({
    providedIn: 'root'
})
export class LoginQL extends Mutation {
    document = gql`
    mutation($username:ID!,$password:String!) {
        logIn(username:$username, password:$password){
          customer{
            name
          }
          token
        }
      }
`;
}

```


Let's add the login method to account service and store the token for future requests.



```javascript
  login(uname: string, pass: string) {
    return this.loginQL.mutate({
      username: uname,
      password: pass
    }
    ).pipe(tap(success =>
      this.token = (success as any).data.logIn.token
    ));
  }
```


Now we should be able to call this method from the login component and on successful login, navigate the user to the list screen.




```javascript
  onSubmit() {
    this.errorMessage = null;
    this.accountService.login(this.model.username, this.model.password).subscribe(c => {
      this.router.navigateByUrl('/list');
    }, e => this.errorMessage = e.message);
  }
```



For this demo purpose I have added a user name "jsmith" and password "pass" using the playground. You should be able to use this credentials to login.

## CheckIn/CheckOut
Since now we have the user token, we should  be able to Check In/Check Out pets from the library. Let's add a check in mutation under queries folder.



```javascript
import { Injectable } from '@angular/core';
import { Mutation } from 'apollo-angular';
import gql from 'graphql-tag';

@Injectable({
    providedIn: 'root'
})
export class CheckInQL extends Mutation {
    document = gql`
    mutation CheckIn($petId:ID!) {
        checkIn(id:$petId){
            pet {
                id
                name
                status
            }
            checkOutDate
            checkInDate
            late
        }
      }
`;
}

```



and also update our pet service method.


```javascript
  checkIn(pId: string) {
    this.checkInQL.mutate({
      petId: pId
    },
      { refetchQueries: [`petsQuery`] }
    ).subscribe();

  }
```



We can call this method from our List when the user clicks on the Check In link.



```javascript
  checkInOut(pet: any) {
    if (pet.status === 'AVAILABLE') {
      this.petService.checkOut(pet.id);
    } else {
      this.petService.checkIn(pet.id);
    }
  }
```



Since we need to modify every Check In request with the user token, we will add a middleware like shown below. We also need to pass AccountService to the middleware, since the user token is stored there.



```javascript
const middleware = (acctService: AccountService) => new ApolloLink((operation, forward) => {
  if (acctService && acctService.token) {
    operation.setContext({
      headers: new HttpHeaders().set('Authorization', `Bearer ${acctService.token}`)
    });
  };
  return forward(operation);
});

export function createApollo(httpLink: HttpLink, acctService: AccountService) {
  return {
    link: middleware(acctService).concat(httpLink.create({ uri })),
    cache: new InMemoryCache(),
  };
}
```



Don't forget to add account service to dependencies in module declaration.



```javascript
@NgModule({
  exports: [ApolloModule, HttpLinkModule],
  providers: [
    {
      provide: APOLLO_OPTIONS,
      useFactory: createApollo,
      deps: [HttpLink, AccountService],
    },
  ],
})
```



You can view the full source code [here](https://github.com/rajvirtual/angular-graphql-apollo).
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
rajvirtual
Raj Vijay

Posted on December 19, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related