In this article, I will show you how to run GraphQL mutations and authentication flows in Angular using Apollo Angular client.
GraphQL is a specification that defines a type system, query language, and schema language for building web APIs. The specification is language agnostic, but in this article you will use a GraphQL API built in JavaScript to build an Angular app that will communicate with the API. We will be working with Apollo Angular, which is an Apollo client integration for Angular. It allows you to query any GraphQL server and build reactive UI using the Angular framework.
We will build an Angular app that can query for and create books. To be precise, we will focus on GraphQL mutation operations by using the mutation API from the Apollo service and how to configure Apollo client to provide authentication credentials when sending the queries.
We will be using an already-built GraphQL server, which you can download on GitHub. Follow the setup instructions to set it up and start it.
This article assumes some knowledge of GraphQL, Angular, and how to work with the Angular CLI. If you’re not familiar with those, I’ve got you covered! I have recently written about the fundamental GraphQL concepts and how to build a GraphQL API. It’ll work you through the specification and the query language. I’ve also written about Angular and how to use the CLI. If you’re comfortable with those, you can continue reading.
We’re going to use the Angular app that was built for the article titled Working With GraphQL In Angular: How to Make a GraphQL Query. This article builds on the knowledge from that one and we’ll be adding the feature to allow users to submit data for new books.
We’re going to clone the GitHub project and install the dependencies by running the commands
git clone https://github.com/pmbanugo/graphql-angular-intro.git
cd graphql-angular-intro
npm install
We’re going to add a new component by running the command ng g c create --module app
. This generates a component that we will use to display a form to collect and save data with the GraphQL service. We want users to navigate to this page through the navigation bar. To do this, open app-routing-module.ts and add a route definition for it:
{ path: "create", component: CreateComponent },
We will edit the component’s HTML template to have the markup below. Open src/app/create.component.html and paste the markup below in it:
<h3>Save Book</h3>
<form (ngSubmit)="onSubmit()">
<div class="form-row">
<div class="form-group col-md-6">
<label for="title">Title</label>
<input
type="text"
class="form-control"
name="title"
[(ngModel)]="title"
/>
</div>
<div class="form-group col-md-6">
<label for="authors">Authors</label>
<input
type="text"
class="form-control"
name="authors"
[(ngModel)]="authors"
/>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="pages">Pages</label>
<input
type="number"
class="form-control"
name="pages"
[(ngModel)]="pages"
/>
</div>
<div class="form-group col-md-6">
<label for="chapters">Chapters</label>
<input
type="number"
class="form-control"
name="chapters"
[(ngModel)]="chapters"
/>
</div>
</div>
<button type="submit" class="btn btn-primary">
Submit
</button>
</form>
The code above will render a form to collect a book’s title, author, number of pages and chapters, as required by the API. We will modify the component’s logic to send that information to the server using the Apollo service. Open src/app/create.component.ts and import the Apollo service, graphql-tag, and the query to submit the mutation.
import { Apollo } from "apollo-angular";
import gql from "graphql-tag";
const submitBook = gql`
mutation submitBook(
$title: String!
$authors: [String!]!
$pages: Int
$chapters: Int
) {
book(title: $title, authors: $authors, pages: $pages, chapters: $chapters) {
id
}
}
`;
const getBooksQuery = gql`
{
books {
title
authors {
name
}
}
}
`;
Next, we will update the class definition with the code below:
export class CreateComponent {
title: string;
authors: string;
pages: number;
chapters: number;
constructor(private apollo: Apollo) {}
onSubmit() {
this.apollo
.mutate({
mutation: submitBook,
variables: {
title: this.title,
authors: this.authors.split(","),
pages: this.pages,
chapters: this.chapters
},
update: (store, mutationResult) => {
// Read the data from our cache for this query.
const data = store.readQuery({
query: getBooksQuery
});
// Add the book from the mutation to the list of books in the cache.
data.books = [...data.books, mutationResult.data.book];
// Write the data back to the cache.
store.writeQuery({
query: getBooksQuery,
data
});
}
})
.subscribe(
({ data }) => {
alert("Book Saved!")
},
error => {
console.log("there was an error sending the query", error);
}
);
}
}
In the code above, we added properties that will be bound to the form input controls and a method onSubmit()
which will be called when the form is submitted. In the onSubmit()
method, we call this.apollo.mutate()
method to perform the mutation operation. We pass to it an object with a mutation
property referencing the submitBook
variable which contains the query definition, and a variables
property whose value is an object with properties matching the variables we defined in the query.
We also specified the update
property, which is a function that we can use to update the Apollo cache based on the result of the mutation. The Apollo cache may already have cached the result of fetching the list of books and, if we add a new book, we want it to be part of the list. It wouldn’t know that it should add the newly created book to the cache, that’s why we use update
to modify the Apollo cache to include it when the operation completes. If we don’t do this, when the user goes to see the list of books, the book that was added won’t be on the list.
In the update
function, we fetch the data for the query that fetches the list of books, add the new book to the list, and then update the cache by calling store.writeQuery
. The getBooksQuery
is the same query used in the Home
component but copied over to this file. A common way to avoid duplication and mistakes is to define the queries in a file and import them where they’re needed.
With the code we have, we can test out this functionality. But, we’ll get an error because that operation requires the user to be authenticated. So let’s add sign-in and sign-out functionality to the app.
The GraphQL API allows only authenticated users to call the book
mutation operation. This is done by verifying the JWT in the authentication header when the request is made. We will configure Apollo client’s network interface layer to include the authorization header if it’s available. This network layer is called Apollo Link. Apollo Link can be used to create middleware that lets you modify requests before they are sent to the server. It’s already installed, but we will change the configuration.
Open src/graphql.module.ts and update the createApollo
function:
export function createApollo(httpLink: HttpLink) {
// Get the authentication token from local storage if it exists
const token = localStorage.getItem("token");
const auth = setContext((operation, context) => {
if (token)
return {
headers: {
Authorization: `Bearer ${token}`
}
};
});
const link = ApolloLink.from([auth, httpLink.create({ uri })]);
return {
link: link,
cache: new InMemoryCache()
};
}
The code you added checks for the JWT in the localStorage and if it exists, it adds it to the HTTP headers by calling setContext
method. After that, it creates an instance of Apollo Link and then returns an object which contains keys for Apollo Link and the cache.
We used setContext
and ApolloLink
so let’s add the imports for them.
import { setContext } from "apollo-link-context";
import { ApolloLink } from "apollo-link";
We do not have the apollo-link-context package installed, but we will install it later. For now, let’s add a service that’ll handle our sign-in and sign-out process. To generate the service, run the command ng g s auth
, open the generated file and paste the code below in it.
import { BehaviorSubject } from "rxjs";
import { Apollo } from "apollo-angular";
import gql from "graphql-tag";
const signin = gql`
mutation signin($email: String!, $password: String!) {
signin(email: $email, password: $password) {
token
user {
name
}
}
}
`;
In the code above, we added import statements for needed modules and defined a variable to hold the query that’ll be used to sign in and get the authentication token. Next, we’ll add functions for sign-in and sign-out to the service definition.
export class AuthService {
isAuthenticated: BehaviorSubject<boolean> = new BehaviorSubject(false);
constructor(private apollo: Apollo) {
if (localStorage.getItem("token")) this.isAuthenticated.next(true);
else this.isAuthenticated.next(false);
}
signin(email: string, password: string) {
this.apollo
.mutate({
mutation: signin,
variables: { email, password }
})
.subscribe(
({ data }) => {
localStorage.setItem("token", data.signin.token);
this.isAuthenticated.next(true);
window.location.href = "/";
},
error => {
console.log("there was an error sending the query", error);
}
);
}
signout() {
localStorage.removeItem("token");
this.isAuthenticated.next(false);
window.location.href = "/";
}
}
The AuthService
provides the methods signin
and signout
. The signin
method calls apollo.mutate
to query the server and, when the request succeeds, we store the returned token in localStorage and then call window.location.href = "/"
to refresh the page which will re-initialize the Apollo client with the new credentials. The signout
method removes the token from localStorage and also redirects to the home page with a browser refresh.
We will now create a Signin
component that’ll be used to collect the user’s email and password and then use that to get the authentication token. Open your command line and run ng g c signin --module app
. Now open the template file for this component and put the markup below in it.
<div class="text-center">
<form class="form-signin" (ngSubmit)="onSubmit()">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="email" class="sr-only">Email address</label>
<input
type="email"
name="email"
class="form-control"
placeholder="Email address"
required
autofocus
[(ngModel)]="email"
/>
<label for="password" class="sr-only">Password</label>
<input
type="password"
name="password"
class="form-control"
placeholder="Password"
required
[(ngModel)]="password"
/>
<button class="btn btn-lg btn-primary btn-block" type="submit">
Sign in
</button>
</form>
</div>
Open signin.component.ts and update the class with the code below:
export class SigninComponent {
email: string;
password: string;
constructor(private authService: AuthService) {}
onSubmit() {
this.authService.signin(this.email, this.password);
}
}
The code above defines the onSubmit
method that gets called when the form is submitted. The method calls the signin
method in the AuthService
. Since we made reference to the AuthService
, let’s import the service. Add the import statement below to the file:
import { AuthService } from "../auth.service";
Next, we will add route definition for the path /signin
. Open app-routing.module.ts and add the snippet below as part of the routes
array on line 7:
{ path: "signin", component: SigninComponent },
Then add an import statement for the component:
import { SigninComponent } from "./signin/signin.component";
Now that we’ve added the Signin
component and added a route for it, let’s update the navigation header to include a SignIn and SignOut button. Open app.component.html and add the code below after line 26.
<a
*ngIf="!isLoggedIn; else loggedOut"
class="nav-item nav-link"
routerLink="/signin"
>Sign In</a
>
<ng-template #loggedOut>
<button class="btn btn-link" (click)="signout()">Sign Out</button>
</ng-template>
Lastly, let’s update the component’s logic to include the property and method we referenced in the markup above. Open app.component.ts, then add the import statement to the AuthService
and update the class definition:
import { AuthService } from "./auth.service";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
isLoggedIn: boolean;
constructor(private authService: AuthService) {
this.authService.isAuthenticated.subscribe(value => {
this.isLoggedIn = value;
});
}
signout() {
this.authService.signout();
}
}
The code we added to the class defines a signout
method that in turn calls the signout
method in the AuthService
, and we set the isLoggedIn
property when the value for authService.isAuthenticated
changes.
Now we can test the newly added functionalities by running the app. Before we start the app, let’s add the apollo-link-context
package which we referenced in graphql.module.ts. Open your command-line application and run npm i apollo-link-context
. To start the application, download this GraphQL server project and follow the instruction to start it up. When it’s started, open your command line to the directory of your Angular project and run ng serve -o
.
So far we’ve built an Angular app that uses GraphQL query and mutation to fetch and save data. You saw how we can use update
to update the Apollo store after a mutation. We also added authentication flow to the app so that we can attach the authorization header to the GraphQL requests and be able to call the mutation operations that requires users to be authenticated. We added sign-in and sign-out functionality, but I skipped the sign-up process. The GraphQL API has a signup
mutation operation which you can use for that, so feel free to implement that yourself.
Here are the links to the Angular and Node projects we used:
Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.