Skip to content

Commit 2ba4338

Browse files
committed
Further refactor GraphQLService and correct permissions in readme
1 parent a66537f commit 2ba4338

File tree

5 files changed

+53
-122
lines changed

5 files changed

+53
-122
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ As originally, the front-end is implemented using [Angular](https://angular.io/t
1919

2020
```sql
2121
CREATE USER test WITH PASSWORD 'testpass';
22-
CREATE DATABASE toh TEMPLATE template0;
23-
GRANT ALL ON DATABASE toh TO test;
2422
\c toh
2523
\i toh.pgsql
24+
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO test;
25+
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO test;
2626
\q
2727
```
2828

2929
- install PostGraphile globally `yarn global add postgraphile` (or `npm -g i postgraphile`)
3030
- optionally generate JSON and GraphQL schema files `postgraphile -c postgres://test:testpass@/toh -s public --export-schema-json toh.json --export-schema-graphql toh.gql`
3131
- run the GraphQL server (in the background) with `postgraphile -c postgres://test:testpass@/toh -s public -o &`
3232
- optionally you can click on the link to GraphiQL generated by the above, and explore the server by entering queries such as: `{allHeroes{nodes{nodeId,id,name}}}`
33-
- install angular CLI globally `yarn global add @angular/cli`
33+
- install angular CLI globally (you may need elevated privileges, e.g. prefix with sudo) `yarn global add @angular/cli`
3434
- if using yarn, configure `ng set --global packageManager=yarn`
3535
- install the front-end dependencies with `yarn` (or `npm i`)
3636
- run the Angular server `ng serve --open`

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "angular-io-example-graphql",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"private": false,
55
"description": "Example Tour of Heroes project from an angular.io guide, but using a real relational database server (PostgreSQL), via GraphQL (generated using PostGraphile).",
66
"scripts": {
@@ -40,7 +40,7 @@
4040
"zone.js": "^0.8.4"
4141
},
4242
"devDependencies": {
43-
"@angular/cli": "^1.6.5",
43+
"@angular/cli": "^1.7.0",
4444
"@types/jasmine": "~2.5.53",
4545
"@types/jasminewd2": "^2.0.3",
4646
"@types/node": "^6.0.45",

src/app/graphql.service.ts

+12-70
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@ import {MessageService} from './message.service';
1111

1212
export const GraphQLUrl = 'http://localhost:5000/graphql'; // URL to web api
1313

14-
export interface GQLOptions {
15-
readAll: any;
16-
readById: any;
17-
readWithTerm: any;
18-
create: any;
19-
update: any;
20-
delete: any;
21-
deleteById: any;
22-
}
23-
2414
@Injectable()
2515
export class GraphQLService {
2616

@@ -35,69 +25,21 @@ export class GraphQLService {
3525
});
3626
}
3727

38-
/** Read all records from the server */
39-
readAll<T>(options: GQLOptions): Observable<T> {
40-
return this.apollo.subscribe({query: options.readAll}).pipe(
41-
map(({data}) => data),
42-
tap(_ => this.log(`read all`)),
43-
catchError(this.handleError<T>('readAll'))
44-
);
45-
}
46-
47-
/** Read a record by id. Will 404 if id not found */
48-
readById<T>(options: GQLOptions, id: any): Observable<T> {
49-
return this.apollo.query<T>({query: options.readById, variables: {id: id}}).pipe(
50-
map(({data}) => data), // returns a {0|1} element array
51-
tap(_ => this.log(`read record with id=${id}`)),
52-
catchError(this.handleError<T>(`readById id=${id}`))
53-
);
54-
}
55-
56-
/* Read all records whose name contains the search term */
57-
readWithTerm<T>(options: GQLOptions, term: string): Observable<T> {
58-
if (!term.trim()) {return of();} // if not search term, return empty array of records.
59-
return this.apollo.watchQuery<T>({query: options.readWithTerm, variables: {term: term}}).valueChanges.pipe(
60-
map(({data}) => data),
61-
tap(_ => this.log(`read records matching "${term}"`)),
62-
catchError(this.handleError<T>('readWithTerm'))
63-
);
64-
}
65-
66-
//////// Save methods //////////
67-
68-
/** Create a new record on the server */
69-
create<T>(options: GQLOptions, record: any): Observable<T> {
70-
return this.apollo.mutate<T>({mutation: options.create, variables: record}).pipe(
71-
map(({data}) => data), // returns a {0|1} element array
72-
tap(_ => this.log(`created record with ${JSON.stringify(record)}`)),
73-
catchError(this.handleError<T>('create'))
74-
);
75-
}
76-
77-
/** Update the record on the server */
78-
update<T>(options: GQLOptions, record: any): Observable<T> {
79-
return this.apollo.mutate<T>({mutation: options.update, variables: record}).pipe(
80-
map(({data}) => data),
81-
tap(_ => this.log(`updated record with ${JSON.stringify(record)}`)),
82-
catchError(this.handleError<T>('update'))
83-
);
84-
}
85-
86-
/** Delete the record from the server */
87-
delete<T>(options: GQLOptions, record: any): Observable<T> {
88-
return this.apollo.mutate<T>({mutation: options.delete, variables: record}).pipe(
28+
/* Query (read) record(s), matching variables where necessary */
29+
query<T>(query: any, variables: any = {}, description: string = 'read'): Observable<T> {
30+
return this.apollo.subscribe({query: query, variables: variables}).pipe(
8931
map(({data}) => data),
90-
tap(_ => this.log(`deleted record with ${JSON.stringify(record)}`)),
91-
catchError(this.handleError<T>('delete'))
32+
tap(_ => this.log(`${description} record(s) with ${JSON.stringify(variables)}`)),
33+
catchError(this.handleError<T>(`Query=${JSON.stringify(query)} Variables=${JSON.stringify(variables)} Description=${description}`))
9234
);
9335
}
9436

95-
/** Delete the record from the server */
96-
deleteById<T>(options: GQLOptions, id: any): Observable<T> {
97-
return this.apollo.mutate<T>({mutation: options.deleteById, variables: {id: id}}).pipe(
37+
/** Mutate (create, update or delete) a record on the server */
38+
mutate<T>(mutation: any, variables: any = {}, description: string = 'mutate'): Observable<T> {
39+
return this.apollo.mutate<T>({mutation: mutation, variables: variables}).pipe(
9840
map(({data}) => data),
99-
tap(_ => this.log(`deleted record with id=${id}`)),
100-
catchError(this.handleError<T>('deleteById'))
41+
tap(_ => this.log(`${description} record(s) with ${JSON.stringify(variables)}`)),
42+
catchError(this.handleError<T>(`Mutate=${JSON.stringify(mutation)} Variables=${JSON.stringify(variables)} Description=${description}`))
10143
);
10244
}
10345

@@ -122,7 +64,7 @@ export class GraphQLService {
12264
}
12365

12466
/** Log a GraphQLService message with the MessageService */
125-
private log(message: string) {
126-
this.messageService.add('GraphQLService: ' + message);
67+
log<T>(item: T, prefix: string = ''): T {
68+
return this.messageService.log(item, 'GraphQLService: ' + prefix);
12769
}
12870
}

src/app/hero.service.ts

+31-47
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,66 @@
11
import { Injectable } from '@angular/core';
2-
import { HttpClient, HttpHeaders } from '@angular/common/http';
3-
import { Apollo } from 'apollo-angular';
4-
import { HttpLink } from 'apollo-angular-link-http';
5-
import { InMemoryCache } from 'apollo-cache-inmemory';
62
import gql from 'graphql-tag';
7-
83
import { Observable } from 'rxjs/Observable';
94
import { of } from 'rxjs/observable/of';
10-
import { catchError, map, tap } from 'rxjs/operators';
11-
5+
import { map, tap } from 'rxjs/operators';
126
import { Hero } from './hero';
13-
import { MessageService } from './message.service';
14-
import { GraphQLService, GQLOptions } from './graphql.service';
15-
16-
const options: GQLOptions = {
17-
readAll: gql`query readAll{allHeroes{nodes{id,name}}}`,
18-
readById: gql`query readById($id:Int!){heroById(id:$id){id,name}}`,
19-
readWithTerm: gql`query readWithTerm($term:String!){allHeroes(term:$term){nodes{id,name}}}`,
20-
create: gql`mutation create($name:String!)
21-
{createHero(input:{clientMutationId:"toh-createHero",hero:{name:$name}})
22-
{clientMutationId,hero{id,name}}}`,
23-
update: gql`mutation update($id:Int!,$name:String!)
24-
{updateHeroById(input:{clientMutationId:"toh-updateHero",id:$id,heroPatch:{name:$name}})
25-
{clientMutationId,hero{id,name}}}`,
26-
delete: gql`mutation delete($id:Int!)
27-
{deleteHeroById(input:{clientMutationId:"toh-deleteHero",id:$id})
28-
{clientMutationId,hero{id,name}}}`,
29-
deleteById: gql`mutation deleteById($id:Int!)
30-
{deleteHeroById(input:{clientMutationId:"toh-deleteHeroById",id:$id})
31-
{clientMutationId,hero{id,name}}}`
32-
};
7+
import { GraphQLService } from './graphql.service';
338

349
@Injectable()
3510
export class HeroService {
3611

3712
constructor(
38-
private graphQLService: GraphQLService
13+
private gqlService: GraphQLService
3914
) { }
4015

4116
/** Get all heroes from the server */
4217
getHeroes (): Observable<Hero[]> {
43-
return this.graphQLService.readAll<{allHeroes:{nodes:Hero[]}}>(options).pipe(
44-
map((data) => data.allHeroes.nodes))
45-
}
18+
return this.gqlService.query<{allHeroes:{nodes:Hero[]}}>(
19+
gql`query readAllHeroes{allHeroes{nodes{id,name}}}`
20+
).pipe(map((data) => data.allHeroes.nodes))}
4621

4722
/** Get a hero by id. Will 404 if id not found */
4823
getHero(id: number): Observable<Hero> {
49-
return this.graphQLService.readById<{heroById:Hero}>(options, id).pipe(
50-
map((data) => data.heroById))
51-
}
24+
return this.gqlService.query<{heroById:Hero}>(
25+
gql`query readHeroById($id:Int!){heroById(id:$id){id,name}}`,
26+
{'id': id},
27+
'readHeroById'
28+
).pipe(map((data) => data.heroById))}
5229

5330
/* Get all heroes whose name contains search term */
5431
searchHeroes(term: string): Observable<Hero[]> {
5532
if (!term.trim()) {return of([]);} // if not search term, return empty hero array.
56-
return this.graphQLService.readWithTerm<{allHeroes:{nodes:Hero[]}}>(options, term).pipe(
57-
map((data) => data.allHeroes.nodes))
58-
}
33+
return this.gqlService.query<{herowithterm:{nodes:Hero[]}}>(
34+
gql`query readHeroesWithTerm($term:String!){herowithterm(term:$term){nodes{id,name}}}`,
35+
{'term': term},
36+
'readHerosWithTerm'
37+
).pipe(map((data) => data.herowithterm.nodes))}
5938

6039
//////// Save methods //////////
6140

6241
/** Add a new hero to the server */
6342
addHero (hero: Hero): Observable<Hero> {
64-
return this.graphQLService.create<{createHero:{hero:Hero}}>(options, hero).pipe(
65-
map((data) => data.createHero.hero))
66-
}
43+
return this.gqlService.mutate<{createHero:{hero:Hero}}>(
44+
gql`mutation create($name:String!)
45+
{createHero(input:{hero:{name:$name}})
46+
{hero{id,name}}}`,
47+
hero, 'create').pipe(map((data) => data.createHero.hero))}
6748

6849
/** Delete the hero from the server */
6950
deleteHero (hero: Hero | number): Observable<Hero> {
7051
const id = typeof hero === 'number' ? hero : hero.id;
71-
return this.graphQLService.delete<{deleteHeroById:{hero:Hero}}>(options, {"id": id}).pipe(
72-
map((data) => data.deleteHeroById.hero))
73-
}
74-
52+
return this.gqlService.mutate<{deleteHeroById:{hero:Hero}}>(
53+
gql`mutation delete($id:Int!)
54+
{deleteHeroById(input:{id:$id})
55+
{hero{id,name}}}`,
56+
{'id': id}, 'delete').pipe(map((data) => data.deleteHeroById.hero))}
7557

7658
/** Update the hero on the server */
7759
updateHero (hero: Hero): Observable<Hero> {
78-
return this.graphQLService.update<{updateHeroById:{hero:Hero}}>(options, hero).pipe(
79-
map((data) => data.updateHeroById.hero))
80-
}
60+
return this.gqlService.mutate<{updateHeroById:{hero:Hero}}>(
61+
gql`mutation update($id:Int!,$name:String!)
62+
{updateHeroById(input:{id:$id,heroPatch:{name:$name}})
63+
{hero{id,name}}}`,
64+
hero, 'update').pipe(map((data) => data.updateHeroById.hero))}
8165

8266
}

src/app/message.service.ts

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export class MessageService {
88
this.messages.push(message);
99
}
1010

11+
log<T>(item: T, prefix: string = 'Log: '): T {
12+
this.add(`${prefix}${typeof(item) === 'string' ? item : JSON.stringify(item)}`);
13+
return item;
14+
}
15+
1116
clear() {
1217
this.messages = [];
1318
}

0 commit comments

Comments
 (0)