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!