Build Solid Web Apps using Angular and GraphQL

Learn the basics of Angular and GraphQL client applications by using the Apollo GraphQL library.

by Gion Kunz & Tomas Trajan

Gion Kunz

  • Angular GDE since 2018
  • Front-end developer, web standards advocate
  • Co-Organizer Angular Zurich Meetup
  • Specialized in SPA development using Angular
  • Onsite ramp-ups, coaching and implementation support in projects
  • Author of the book "Mastering Angular Components"
  • Frequent speaker at conferences and meetups
  • Teacher for web development and UX engineering at the SAE Institute in Zurich
  • Tutor at O'Reilly Safari Online Training
  • Contributor on the Angular project, author of Chartist.js library

Founder, UI Developer, UX Enthusiast at syncrea GmbH

Tomas Trajan

  • Google Developer Expert for Angular
  • Angular Zurich Co-organizer
  • Angular CLI contributor
  • Angular focused blog (more then 2.3 M views)
  • Creator of Angular Ngrx Material Starter (30+ contributors)
  • Creator of @angular-extensions/model and @angular-extensions/elements libraries
  • International speaker
  • Angular trainer (on site Angular and NgRx (state management) workshops)

Angular focused Software Consultant, Speaker, Trainer, Blogger

Next Meetup, Tomorrow 25. September 2019:
"How to generate Angular REST Clients with OpenAPI Generator"

Topics

  • Introduction to the GraphQL Platform
  • Creating a simple server
  • Basic Intro about Angular
  • UI Framework Comparison
  • Creating a simple TODO App using Apollo Client and Angular Material Design

What to Expect!

  • We'll not dive deep on Angular
  • Our main goal is to create an end-to-end App using Angular, Angular Material and the Apollo GraphQL Platform
  • We're building everything with TypeScript... Yes, EVERYTHING... even the Node.js server!

Tooling

Node.js & NPM

  • Used for Tooling (TypeScript, Webpack, SystemJS, AoT Compiler etc.)
  • Can be used for Server-Side-Rendering
  • Angular CLI runs on Node.js
  • NodeJs Apollo GraphQL server and TypeScript type definitions
  • Version > 10.9.0 is currently used in Angular 8

Lab 1

Complete local setup to create NodeJS projects and prepare for Angular and the Apollo Platform.

Prerequisites

Apollo Platform

Apollo is a platform for building a data graph, a communication layer that seamlessly connects application clients (such as React and iOS apps) to back-end services.

Lab 2

Create a GraphQL server with Apollo Server and Express.

Create Folders

# Create project folder
mkdir graphql-todo && cd graphql-todo

# Create sub-folder for our server
mkdir graphql-todo-server && cd graphql-todo-server

Initialize Node.js Project

npm init

# Install typescript, nodemon and node-ts
npm i typescript nodemon ts-node --save-dev

# Initialize TypeScript project
npx tsc --init --rootDir src --outDir dist --lib dom,es6 --module commonjs --removeComments

Install Apollo Dependencies

# Apollo Platform and Server dependencies
npm i apollo-server-express cors express graphql http ncp uuid --save

# Typing files for Node.js and dependencies
npm i @types/express @types/graphql @types/uuid @types/node graphql-import graphql-import-node --save-dev

Add proper start script

// package.json
// ...
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon \"src/index.ts\" --exec \"ts-node\" src/index.ts -e ts,graphql"
  },
// ...

Create our Apollo GraphQL Server

// src/index.ts

import express from 'express';
import {ApolloServer} from 'apollo-server-express';
import {createServer} from 'http';
import cors from 'cors';
import {schema} from './schema';

const host = '0.0.0.0';
const port = 8080;
const app = express();
const server = new ApolloServer({schema});
app.use('*', cors());
server.applyMiddleware({app, path: '/graphql'});
const httpServer = createServer(app);
httpServer.listen(
  {host, port}, 
  (): void => console.log(`\nšŸš€      GraphQL is now running on http://localhost:${port}/graphql`)
);

Add "Hello World" GraphQL Schema

// src/schema.graphql

type Query {
  helloWorld: String!
}

Compile Schema and Implement resolvers

// src/schema.ts

import 'graphql-import-node';
import * as typeDefs from './schema.graphql';
import {makeExecutableSchema} from 'graphql-tools';
import {GraphQLSchema} from 'graphql';
import {IResolvers} from 'graphql-tools';

export const resolvers: IResolvers = {
  Query: {
    helloWorld() {
      return `šŸ‘‹ Hello world! šŸ‘‹`;
    }
  }
};

export const schema: GraphQLSchema = makeExecutableSchema({
  typeDefs,
  resolvers
});

Visit the Developer UI of Apollo to run queries and download the schema of your server.
http://localhost:8080/graphql

Angular Overview

Key Features

  • CLI Tool
  • TypeScript
  • Multi-Platform Support (Web Workers, Server, NativeScript)
  • Build on Web Components Concepts (Shadow DOM)
  • Component System using composition
  • Ahead of time compilation (AoT)
  • Server-Side Rendering (SSR)
  • PWA Support
  • Router with Lazy Loading

Angular

React

Vue.js

Component Rendering

Router

TypeScript Friendliness

Animation

Multi-Platform

Server Side Rendering

State Management

Core Support

Core Support

Core Support

Written in TypeScript

TypeScript JSX and good support

Good support

Core Support

react-router

vue-router

Core Support

react-move, react-motion and others

Core Support

vue-server-renderer

Core Support

Virtual DOM, react native

Render Adapter, Web Worker, Server, Native

Weex, supported but limited

Virual DOM, roll-your-own, Next.js

ngrx store 3rd party

Redux 3rd party

vuex

Angular is an all-round carefree package with optional customization

Angular Todo App

Angular CLI

  • Quickly get started with Angular
  • Whole tooling ecosystem including
  • TypeScript, Karma, Protractor E2E, AoT Compilation and more
  • Generator for project maintenance

Lab 3

Create our first Angular App using the Angular CLI.

Install Angular CLI and Create App

# Install Angular CLI as global module
npm i @angular/cli --global

# Create todo app (in the graphql-todo folder!)
ng new graphql-todo-app
# ? Would you like to add Angular routing? No
# ? Which stylesheet format would you like to use? CSS

# Enter folder and serve app
cd graphql-todo-app && ng serve

Angular Material is an Implementation of Material Design UI components for Angular

Let's add Angular Material

# Using the CLI we can add ng addable libraries
ng add @angular/material

# ? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink
# ? Set up HammerJS for gesture recognition? Yes
# ? Set up browser animations for Angular Material? Yes

Specify the components we need

// src/app/app.module.ts
...
import {MatListModule, MatToolbarModule, MatFormFieldModule, MatCheckboxModule, MatInputModule, MatButtonModule} from '@angular/material';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    MatToolbarModule,
    MatButtonModule,
    MatInputModule,
    MatFormFieldModule,
    MatCheckboxModule,
    MatListModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Build a simple Todo App UI template

<!-- src/app/app.component.html -->

<mat-toolbar class="header">
  <span role="header" color='primary'>GraphQL Todo</span>
</mat-toolbar>
<main class="container">
  <div class="add-todo">
    <mat-form-field class="add-todo-field">
      <input #todoInput matInput 
             (keyup.enter)="addTodo(todoInput.value); todoInput.value = ''"
             placeholder="Today, I learn about Angular, GraphQL and TypeScript!">
    </mat-form-field>
    <button mat-mini-fab (click)="addTodo(todoInput.value); todoInput.value = ''">
      <i class="material-icons">add</i>
    </button>
  </div>
  <mat-list>
    <mat-list-item *ngFor="let todo of todoItems" class="list">
      <mat-checkbox color='primary'>
        {{ todo }}
      </mat-checkbox>
    </mat-list-item>
  </mat-list>
</main>

Implement Todo Logic

// src/app/app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  todoItems = ['Go for a walk with my dog', 'Buy some bread and butter', 'Learn how to paint'];

  addTodo(title: string) {
    this.todoItems = [title, ...this.todoItems];
  }
}

Using the Apollo Angular Client

Lab 4

Connect our Angular App to our Apollo GraphQL server.

Install GraphQL CLI and Initialize Project

# Install graphql-cli for tooling
npm i graphql-cli --global

# Initialize graphql client project (within graphql-todo-app folder)
graphql init

# ? Enter project name (Enter to skip): 
# ? Local schema file path: ../graphql-todo-server/src/schema.graphql
# ? Endpoint URL (Enter to skip): http://localhost:8080/graphql
# ? Name of this endpoint, for e.g. default, dev, prod: default
# ? Subscription URL (Enter to skip): 
# ? Do you want to add other endpoints? No
# ? What format do you want to save your config in? JSON

Install GraphQL Angular Client

# Install Apollo Angular
ng add apollo-angular

Change Endpoint URL Constant

// src/app/graphql.module.ts

import {NgModule} from '@angular/core';
import {ApolloModule, APOLLO_OPTIONS} from 'apollo-angular';
import {HttpLinkModule, HttpLink} from 'apollo-angular-link-http';
import {InMemoryCache} from 'apollo-cache-inmemory';

const uri = 'http://localhost:8080/graphql';
export function createApollo(httpLink: HttpLink) {
  return {
    link: httpLink.create({uri}),
    cache: new InMemoryCache(),
  };
}

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

Obtain "Hello World" Message from Server

// src/app/app.component.ts

import {Component} from '@angular/core';
import {Apollo} from 'apollo-angular';
import gql from 'graphql-tag';

const helloWorld = gql`
  {
    helloWorld
  }
`;

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  todoItems = ['Go for a walk with my dog', 'Buy some bread and butter', 'Learn how to paint'];
  message: string;

  constructor(private apollo: Apollo) {
    this.apollo
      .watchQuery<{helloWorld: string}>({query: helloWorld})
      .valueChanges
      .subscribe(result => {
        this.message = result.data.helloWorld;
      });
  }

  addTodo(title: string) {
    this.todoItems = [title, ...this.todoItems];
  }
}

Implement Todo Client and Server

Lab 5

Implement Todo App Client and Server using Todo model and updated GraphQL Schema.

Update our GraphQL Schema on the Server

// graphql-todo-server/src/schema.graphql

type Query {
  getTodos: [Todo!]!
}
  
type Todo {
  id: ID!
  title: String!
  done: Boolean!
}

Add Model and Update Resolvers for new Query

// graphql-todo-server/src/schema.ts
 
import 'graphql-import-node';
import * as typeDefs from './schema.graphql';
import {makeExecutableSchema} from 'graphql-tools';
import {GraphQLSchema} from 'graphql';
import {IResolvers} from 'graphql-tools';

export interface Todo {
  id: string;
  title: string;
  done: boolean;
}

export const inMemoryDb: {todos: {[id: string]: Todo}} = {
  todos: {
    '1': {
      id: '1',
      title: 'Todo 1',
      done: false
    }
  }
}

export const resolvers: IResolvers = {
  Query: {
    getTodos(): Todo[] {
      return Object.values(inMemoryDb.todos);
    }
  }
};

export const schema: GraphQLSchema = makeExecutableSchema({
  typeDefs,
  resolvers
});

Obtain Todos in Angular App From Server

// graphql-todo-app/src/app/app.component.ts
 
import { Component } from '@angular/core';
import {Apollo} from 'apollo-angular';
import gql from 'graphql-tag';
import {Todo} from '../../../graphql-todo-server/src/schema';

const allTodosQuery = gql`
  query {
    getTodos {
      id
      title
      done
    }
  }
`;

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  todoItems: Todo[] = [];

  constructor(private apollo: Apollo) {
    this.apollo
      .watchQuery<{getTodos: Todo[]}>({
        query: allTodosQuery
      })
      .valueChanges
      .subscribe(result => {
        this.todoItems = result.data.getTodos;
      });
  }

  addTodo(title: string) {
    // TODO: This will be implemented later
  }
}

How cool is that!?

Add Mutations to Schema in the Server

// graphql-todo-server/src/schema.graphql

type Query {
  getTodos: [Todo!]!
}

type Mutation {
  addTodo(title: String!, done: Boolean! = false): [Todo!]!
  updateTodo(id: ID!, done: Boolean!): [Todo!]!
}

type Todo {
  id: ID!
  title: String!
  done: Boolean!
}

Add Mutation Resolvers in the Angular App

// graphql-todo-server/src/schema.ts
 
import 'graphql-import-node';
import * as typeDefs from './schema.graphql';
import {makeExecutableSchema} from 'graphql-tools';
import {GraphQLSchema} from 'graphql';
import {IResolvers} from 'graphql-tools';
import uuid from 'uuid/v4';

export interface Todo {
  id: string;
  title: string;
  done: boolean;
}

export const inMemoryDb: {todos: {[id: string]: Todo}} = {
  todos: {
    '1': {
      id: '1',
      title: 'Todo 1',
      done: false
    }
  }
}

export const resolvers: IResolvers = {
  Query: {
    getTodos(): Todo[] {
      return Object.values(inMemoryDb.todos);
    }
  },
  Mutation: {
    addTodo(_, {title,  done = false}: Todo): Todo[] {
      const id: string = uuid();
      inMemoryDb.todos = {
        ...inMemoryDb.todos,
        [id]: {
          id,
          title,
          done
        }
      };
      return Object.values(inMemoryDb.todos);
    },
    updateTodo(_, {id, done}: {id: string, done: boolean}): Todo[] {
      inMemoryDb.todos = {
        ...inMemoryDb.todos,
        [id]: {
          ...inMemoryDb.todos[id],
          id,
          done
        }
      };
      return Object.values(inMemoryDb.todos);
    }
  }
};

export const schema: GraphQLSchema = makeExecutableSchema({
  typeDefs,
  resolvers
});

Implement add and update Todo Methods

// graphql-todo-app/src/app/app.component.ts
 
import { Component } from '@angular/core';
import {Apollo} from 'apollo-angular';
import gql from 'graphql-tag';
import {Todo} from '../../../graphql-todo-server/src/schema';

const allTodosQuery = gql`
  query {
    getTodos {
      id
      title
      done
    }
  }
`;

const addTodoMutation = gql`
  mutation addTodo($title: String!, $done: Boolean! = false) {
    addTodo(title: $title, done: $done) {
      id
      title
      done
    }
  }
`;

const updateTodoMutation = gql`
  mutation updateTodo($id: ID!, $done: Boolean!) {
    updateTodo(id: $id, done: $done) {
      id
      title
      done
    }
  }
`;

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  todoItems: Todo[] = [];

  constructor(private apollo: Apollo) {
    this.apollo
      .watchQuery<{getTodos: Todo[]}>({
        query: allTodosQuery
      })
      .valueChanges
      .subscribe(result => {
        this.todoItems = result.data.getTodos;
      });
  }

  addTodo(title: string) {
    this.apollo.mutate<{addTodo: Todo[]}>({
      mutation: addTodoMutation,
      variables: {
        title
      }
    }).subscribe(result => {
      this.todoItems = result.data.addTodo;
    })
  }

  updateTodo(id: string, done: boolean) {
    this.apollo.mutate<{updateTodo: Todo[]}>({
      mutation: updateTodoMutation,
      variables: {
        id,
        done
      }
    }).subscribe(result => {
      this.todoItems = result.data.updateTodo;
    })
  }
}

Update Component View to update Todos

<!-- graphql-todo-app/src/app/app.component.html -->
 
<mat-toolbar class="header">
  <span role="header" color='primary'>GraphQL Todo</span>
</mat-toolbar>
<main class="container">
  <div class="add-todo">
    <mat-form-field class="add-todo-field">
      <input #todoInput matInput 
             (keyup.enter)="addTodo(todoInput.value); todoInput.value = ''"
             placeholder="Today, I learn about Angular, GraphQL and TypeScript!">
    </mat-form-field>
    <button mat-mini-fab (click)="addTodo(todoInput.value); todoInput.value = ''">
      <i class="material-icons">add</i>
    </button>
  </div>
  <mat-list #todoist class="todolist">
    <mat-list-item *ngFor="let todo of todoItems" class="list">
      <mat-checkbox color='primary' 
                    [checked]="todo.done" 
                    (change)="updateTodo(todo.id, !todo.done)">
        {{ todo.title }}
      </mat-checkbox>
    </mat-list-item>
  </mat-list>
</main>

Workshop Recap

Topics

  • Introduction to the GraphQL Platform
  • Creating a simple server
  • Basic Intro about Angular
  • UI Framework Comparison
  • Creating a simple TODO App using Apollo Client and Angular Material Design

Resources

Questions?

Let's create together.

Thanks!

Build Solid Web Apps using Angular and GraphQL

By Gion Kunz

Build Solid Web Apps using Angular and GraphQL

How to use Angular and GraphQL to build strongly typed APIs' and rock-solid user interfaces

  • 974