Saturday, September 8, 2018

I saw Alex Winter speak on using GraphQL with Angular at AngularMN on Wednesday night.

GraphQL is a trending approach to data hydration and interactions. Just as SQL stands for Structured Query Language the QL in GraphQL would be for Query Language. GraphQL in tandem with React, as it largely rears its head currently (Tamara Temple had an example at JavaScript MN in adjacent Minneapolis a week earlier), is used heavily at Facebook, but an Angular implementation is certainly not far-fetched. Alex started out by spinning up an Express server for such an orchestration which looked like so and tied into MongoDB with Mongoose.

import express from 'express';
import bodyParser from 'body-parser';
import { graphqlExpress, ApolloServer } from 'apollo-server-express';
import { gql } from 'apollo-server-express';
import axios from 'axios';
 
const TYPEDEFS = gql`
   type Query {
      test_query: Test
   }
   type Test {
      test_field_1: String
      test_field_2: Int
      test_field_3: Boolean
   }
`;
 
const SERVER = new ApolloServer({
   typeDefs: TYPEDEFS,
   resolvers: RESOLVERS,
   playground: {
      endpoint: 'http://localhost:4000/graphql',
      settings: {
         'editor.theme': 'dark'
      }
   }
});
 
const app = express();
 
app.use(cors());
 
mongoose.connect('mongodb://localhost/graphqlserver');
const connection = mongoose.connection;
 
connection.once('open', () => {
   console.log('MongoDB connection has been established successfully');
});
 
SERVER.applyMiddleware({
   app: app
});
 
app.listen(4000, () => {
   console.log('Express server running on port 4000');
});

 
 

We need ApolloServer to crosstalk with GraphSQL, a domain-specific language, and do take note of the playground. The playground may be brought up in a browser and you may thumb through what is in GraphQL that way not unlike using SSMS with T-SQL. Robo 3T is a more formal tool for the same job as the playground, but a neat thing about the playground is that you may share the URL with a client (if you're a consultant) and let them easily play with it and it is elementary. There will be a little "Schema" tab to expand at the far right to see the meat of what's what in JSON form. server.js is given above and here is schema.js which shows off our basic types which will hydrate from Mongo. The app has a superheroes theme based on the Tour of Heroes example that is all too overused.

import { gql } from 'apollo-server-express';
 
const TYPEDEFS = gql`
   type Hero {
      id: String
      name: String
      voteCount: Int
   }
   type Query {
      allHeroes(searchTerm: String): [Hero]
      hero(id: String!): Hero
   }
   type Mutation {
      addHero(name: String!): Hero
      upvote(id: String!): Hero
      downvote(id: String!): Hero
   }
`;
 
export default TYPEDEFS;

 
 

Alright, herein the exclamation mark in the case of String! Implies that the String is required and [Hero] implies an array of Hero objects. This is some of the DSL syntax for GraphQL. Alright, why do this at all? Why do I care? There are a few benefits. With GraphQL, if one were grabbing, say people from a "rolodex" (database) one could just get the properties on the people one cared about instead of doing sort of a SELECT * and getting everything as a property on a JSON-object over the wire. This makes for snappier performance and it is not something you can easily do with REST endpoints. Another man in the crowd Wednesday night spoke to how we, as geek society, embraced REST over SOAP in an attempt to escape all of the heavy XML but ended up missing some of what was good about SOAP, such as type definitions which are really sparse in REST. This individual thought that GraphQL was the happy medium and that would be another point in its favor. I thought of this talk wherein I was told that IQueryable is really bad as it allows for queries to bleed out of the data layer of an app up to the UI and puts us back in the dirty old days of just having SQL inline in a spaghetti mess more or less. However, there are a few reasons to consider GraphQL more elegant than the IQueryable antipattern against the .edmx Entity Framework model as, first of all, GraphQL doesn't have to wrap one database like an .edmx. Instead sources from diversified servers can be queried to make an object in aggregate (different fields from different data sources) in a microservices pattern! Try that with REST or SOAP. Ha! It won't be lightweight. There are analytics tools for the searches one runs to see what one is searching against. There is also the concept of stitching in which you may fetch someone else's GraphQL syntax that is exposed and slurp it into your own. GraphQL for VSCode is a plugin that will color-code the GraphQL syntax in Visual Studio Code. There are other plugins for other IDEs obviously and GraphQL isn't the only one for Visual Studio Code. VSCode GraphQL is another. Graphcool allows for filtering and pagination with GraphQL. You may use GraphQL against what's in your local store in a Redux pattern as well. Alright, I tried to take photos of all of the files Alex showed off and the next one was resolvers.js, speaking of Redux, and I really only was able to capture a piece of the file on my end. It looks like this:

      if (searchTerm !== '') {
         return heroModel
            .find({ $text: { $search: searchTerm } })
            .sort({ voteCount: 'desc' });
      } else {
         return heroModel.find().sort({ voteCount: 'desc' });
      }
   },
   hero: (root, { id }) => {
      return heroModel.findOne({ id: id });
   }
},
Mutation: {
   upVote: (root, { id }) => {

 
 

Alex had the code above up while he was importing code to get GraphQL working with Angular. For example if you want ids to get created for the objects you may loop in "schema defaults" like so:

import uuid from 'uuid';

 
 

Obviously there is an implementation beyond the namespace. Also, Alex did an npm install of apollo-angular, apollo-angular-li, nk-http, apollo-client, apollo-cache-inmemory, graphql-tag, and graphql. Of these, apollo-angular-li, nk-http, apollo-client, apollo-cache-inmemory, graphql-tag, and graphql may just be procured by installing apollo-boost really. Alex's app.module.ts looked like so:

import { BrowseModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 
import { AppComponent } from './app.component';
import { Apollo } from 'apollo-angular';
 
@NgModule({
   declarations: [
      AppComponent
   ],
   import: [
      BrowseModule
   ],
   providers: [],
   bootstrap: [AppComponent]
})
export class AppModule {
   constructor(private apollo: Apollo, private httpLink: HttpLink) {
   apollo.create({
         link: httpLink.create({ url: 'http://localhost:4000/graphql' }),
         cache: new InMemoryCache()
      });
   }
}

 
 

I was only able to capture of piece of tsconfig.json. It looks like so:

      "outDir": "./dist/out-tsc",
      "sourceMap": true,
      "declaration": false,
      "module": "es2015",
      "moduleResolution": "node",
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "target": "es5",
      "typeRoots": [
         "node_modules/@types"
      ],
      "lib": [
         "es2017",
         "dom",
         "esnext"
      ]
   }
}

 
 

The esnext has to get added here for GraphQL to work and the TypeScript version has to be updated to 2.83. Here is app.component.html, a template for a component that Alex advertised.

<mat-list>
   <mat-list-item *ngFor="let hero or heroes | async">
      <h3 matLine>
         <b>{{hero.name}}</b>
      </h3>
      <p matLine>
         <button mat-raised-button color="primary"
               (click)="upvote(hero.id)">Upvote</button>
         <button mat-raised-button color="warn"
               (click)="upvote(hero.id)">Downvote</button>
         Vote Count:
         <span> {{hero.voteCount}} </span>
      </p>
   </mat-list-item>
</mat-list>

 
 

The component itself, app.component.ts, looks like this:

   stylesheets: ['.app.component.css']
})
export class AppComponent implements OnInit {
   title = 'frontend';
   searchTerm = '';
   heroes: Observable<Hero[]>;
 
   constructor(private heroService: HeroService) {}
 
   ngOnInit() {
      this.heroes = this.heroService.getAllHeroes(this.searchTerm);
   }
 
   onKeyUp() {
      this.heroes = this.heroService.getAllHeroes(this.searchTerm);
   }
 
   upvote(id: string) {
      this.heroService.upvoteHero(id).subscribe(
         data => {
            console.log('Upvoted', data);
         },
         error => {
            console.log('Failed to upvote the hero', error);
         }
      );
   }
 
   downvote(id: string) {
      this.heroService.downvoteHero(id).subscribe(
         data => {
            console.log('Downvoted', data);

 
 

The service it talks to, hero.service.ts, looks like so:

import { Injectable } from '@angular/core';
import { Apollo } from 'apollo-angular';
import { Query, Hero } from './hero.model";
import gql from 'graphql-tag';
import { map, filter } from 'rxjs/operators';
 
@Injectable({
   providedIn: 'root'
})
export class HeroService {
   constructor(private apollo: Apollo) {}
 
   getAllHeroes(searchTerm: string) {
      return this.apollo
         .watchQuery<Query>({
            pollInterval: 500,
            query: gql`
               query allHeroes($searchTerm: String) {
                  allHeroes(searchTerm: $searchTerm) {
                     id
                     name
                     voteCount
                  }
               }
            `,
            variables: {
               searchTerm: searchTerm
            }
         })
         .valueChanges.pipe(map(result => result.data.allHeroes));
      }
 
      upvoteHero(id: string) {
         return this.apollo.mutate({
            mutation: gql`
               mutation upvote($id: String!) {
                  upvote(id: $id) {
                     id
                     name
                     voteCount
                  }
               }
            `,
            variables: {
               id: id
            }
         });
      }
 
      downvoteHero(id: string) {
         return this.apollo.mutate({

 
 

A hero model in the Angular code, hero.model.ts, looks like this:

      query: gql`
         query allHeroes($searchTerm: string) {
            allHeroes(searchTerm: $searchTerm) {
               id
               name
               voteCount
            }
         }
      `,
      variables: {
         searchTerm: searchTerm
      }
   })
   .valueChanges.pipe(map(result => result.data.allHeroes));
}
 
upvoteHero(id: string) {
   return this.apollo.mutate({
      mutation: gql`
         mutation upvote($id: String!) {
            upvote(id: $id) {
               id
               name
               voteCount
            }
         }
      `,
      variables: {
         id: id
      }
   });
}
 
downvoteHero(id: string) {
   return this.apollo.mutate({

 
 

Regrettably, I only have partial representations of the last four files, but you get the idea, right? This was my first time to attend AngularMN. It was at Virtuwell's office in St. Paul, Minnesota. Thanks to them and a Michael Jordon, pictured with Alex Winter here, for hosting.

No comments:

Post a Comment