Test Driving Apollo Client 2.0  

by Gerard Sans (@gerardsans)

Google Developer Expert

Master of Ceremonies

Blogger

International Speaker

Spoken at 56 events in 18 countries

Angular Trainer

Community Leader

900

1.2K

Angular In Flip Flops

GraphQL Timeline

2012

GraphQL created at Facebook

2015

GraphQL is open sourced

Relay Classic is open sourced

2016

New GraphQL website graphql.org

First GraphQL Summit

GitHub announces GraphQL API

Angular (v2)

2017

Relay Modern 1.0

Apollo Client 2.0

Angular (v5)

Relay vs Apollo Downloads

launchpad.graphql.com

graphql.com

Developer Tools

Todo App

GraphQL Schema

Type System

  • Scalar Types: Int, Float, String, Boolean, ID 
  • Object Types: Todo
  • Entry points: Query, Mutation, Subscription

Schema Syntax

  • Optional: String, Todo
  • Mandatory: String!, Todo!
  • Arrays: [String], [Todo]
npm install --global graphcool
graphcool init
graphcool push
graphcool console

graphcool-cli

Todos Schema

// project.graphcool

type Todo @model {
  id: ID! @isUnique
  text: String!
  complete: Boolean!
}

demo

Apollo Client

2.0

Apollo

Cache

Apollo Client

Apollo

Link

Apollo Cache

Packages

  • apollo-cache-inmemory
  • apollo-cache-hermes

InMemoryCache

  • Normalised and flattened
  • apollo.getClient()
  • readQuery, writeQuery
  • readFragment, writeFragment

Add Todo

const query = gql`query AllTodos {
  allTodoes { id text complete }
}`;
const data = store.readQuery<Todos>({ query });
const newTodo = {
  id: createTodo.id,
  text: input.value,
  complete: false,
  __typename: "Todo"
};
store.writeQuery({
  query,
  data: {
    allTodoes: [...data.allTodoes, newTodo],
  },
});

Apollo Link

Packages

  • apollo-link
  • apollo-link-http
  • apollo-link-ws
  • apollo-link-error
  • apollo-link-batch
  • apollo-link-polling
  • apollo-link-retry

Packages (future)

  • apollo-link-store
  • apollo-link-rest
  • apollo-link-offline

Apollo Link

  • concat(link1, link2)
  • split(expr, link1, link2)
  • from([link1, link2])

Security Headers

export class GraphQLModule {
  constructor(
    private apollo: Apollo,
    private httpLink: HttpLink
  ) {
    const http = httpLink.create();

    const authMiddleware = new ApolloLink((operation, forward) => {
      operation.setContext({
        headers: { 'Authorization': localStorage.getItem('token') || '' }
      });
      return forward(operation);
    });

    apollo.create({
      link: concat(authMiddleware, http)
    });
  }
}

Using  GraphQL

GraphQL Server

source: blog

Dependencies

Setup

@NgModule({
  imports: [ HttpClientModule, HttpLinkModule ],
  exports: [ ApolloModule ]
})
export class GraphQLModule {
  constructor( apollo: Apollo, httpLink: HttpLink ) {
    apollo.create({
      // localhost/graphql (default)
      link: httpLink.create({ 
        uri: GRAPHCOOL_URI
      }),
      cache: new InMemoryCache(),
    });
  }
}

Bootstrap

// app.module.ts
import { GraphQLModule } from './graphql.module';

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

Todos Schema

schema { 
  query: Query,  
  mutation: Mutation 
}

type Todo {  
  id: ID!
  text: String!
  complete: Boolean!
}

type Query {
  allTodoes(skip: Int, take: Int): [Todo!]!
}

Main APIs

  • query (QueryRef)
  • watchQuery (QueryRef)
  • mutate (Observable)

QueryRef

  • valueChanges (Observable)
  • subscribeToMore()
  • refetch()
  • fetchMore()
  • start/stopPolling()

query

// app.component.ts
@Component()
export class App { 
   constructor(private apollo: Apollo){
    let query = apollo.query({
      query: gql`query todos { 
        allTodoes { id complete text } }`
    });
    let subscription = query.valueChanges.subscribe({
      next: result => {
        // result.loading
        // result.data.allTodoes
      },
      error: error => {
        console.log(`Error: ${error.message}`);
      }
    });
  }
}

watchQuery

// todoList.component.ts
@Component({
  template:
    `<todo *ngFor="let todo of todos | async">{{todo.text}}</todo>`
})
class TodoList implements OnInit {
  todos: Observable<any>;
  constructor(private apollo: Apollo) { }

  ngOnInit() {
    const query = this.apollo.watchQuery({ query: todosQuery });
    this.todos = query.valueChanges.map(({data}) => data.allTodoes);
  }
  // onRefresh = query.refetch;
}

mutate

// todo.component.ts
@Component()
export class Todo {
 constructor(private apollo: Apollo) { }

 private onTodoClick(){ 
  let toggle$ = this.apollo.mutate({
    mutation: gql`
      mutation toggleTodo($id: ID!, $complete: Boolean!) {
        updateTodo(id: $id, complete: $complete) { id text complete }
      }`,
    variables: { id: this.id, complete: !this.complete },
  let subscription = toggle$.subscribe(
    ({ data }) => { // data.updateTodo.id }
  ); 
 }
}

demo

Adding Realtime

GraphQL Server

source: blog

Dependencies

Setup

export class GraphQLModule {
  constructor(
    private apollo: Apollo,
    private httpLink: HttpLink
  ) {
    const link = this.setupLink();
    const cache = new InMemoryCache();
    apollo.create({ link, cache });
  }
}

Setup

private setupLink() {
  const http = this.httpLink.create({ 
    uri: GRAPHCOOL_URI
  });
  const websocket = new WebSocketLink(
    new SubscriptionClient(GRAPHCOOL_URI_WS, {
      reconnect: true
    })
  );
  const link = ApolloLink.split(
    (op) => {
      const ast = getOperationAST(op.query, op.operationName);
      return ast && ast.operation === 'subscription';
    },
    /* if true use  */ websocket,
    /* if false use */ http
  );
  return link;
}

Todos Schema

schema { 
  subscription: Subscription 
}

type Mutation {
  createTodo(text: String!, complete: Boolean!): Todo
  deleteTodo(id: ID!): Todo
}

type Subscription {
  Todo(filter: [CREATED, UPDATED, DELETED]): {
    mutation: [CREATED, UPDATED, DELETED]
    node: Todo
    updatedFields: [String!]
    previousValues: Todo
  }
}

subscribeToMore

// app.component.ts
query.subscribeToMore({
  document: gql`
    subscription {
      Todo(filter: { mutation_in: [CREATED] }) {
        node {
          id
          complete
          text
        }
      }
    }
  `,
  updateQuery: (state: Todos, { subscriptionData }) => {
    ...
  }

subscribeToMore

// app.component.ts
updateQuery: (state, { subscriptionData }) => {
  const {node: node} = (subscriptionData as any).Todo;

  if (!state.allTodoes.find(todo => 
    todo.id === node.id)) {
    state.allTodoes.push(node);                
  }
  return {
    allTodoes: todos
  }
}

demo

Why use GraphQL?

Some reasons

  • Declarative
  • De-coupled from storage
  • Validated and structured
  • Facilitates Collaboration
  • Super fast

More

gsans/todo-apollo-v2