Skip to content

Commit 99fed6e

Browse files
author
Kamil Kisiela
committed
feat(Apollo): Reactive query binding
1 parent b5c87bf commit 99fed6e

File tree

5 files changed

+157
-25
lines changed

5 files changed

+157
-25
lines changed

examples/hello-world/client/main.ts

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,10 @@ import {
55
import {
66
Component,
77
Injectable,
8-
ChangeDetectionStrategy,
98
} from "angular2/core";
109

1110
import {
12-
ApolloQueryPipe,
13-
APOLLO_PROVIDERS,
14-
Angular2Apollo,
15-
defaultApolloClient,
11+
Apollo,
1612
} from 'angular2-apollo';
1713

1814
import ApolloClient, {
@@ -33,28 +29,14 @@ const client = new ApolloClient({
3329
pipes: [ApolloQueryPipe],
3430
})
3531
@Injectable()
32+
@Apollo({
33+
client
34+
})
3635
class Main {
3736
users: Observable<any[]>;
3837

39-
constructor(private angularApollo: Angular2Apollo) {
40-
this.users = angularApollo.watchQuery({
41-
query: `
42-
query getUsers {
43-
users {
44-
firstName
45-
lastName
46-
emails {
47-
address
48-
verified
49-
}
50-
}
51-
}
52-
`,
53-
});
38+
constructor() {
5439
}
5540
}
5641

57-
bootstrap(Main, [
58-
APOLLO_PROVIDERS,
59-
defaultApolloClient(client),
60-
]);
42+
bootstrap(Main);

examples/hello-world/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
},
77
"dependencies": {
88
"angular2": "^2.0.0-beta.15",
9-
"angular2-apollo": "0.0.1",
9+
"angular2-apollo": "file:../../",
1010
"apollo-client": "0.1.2",
1111
"casual-browserify": "^1.5.2",
1212
"express": "^4.13.4",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"apollo-client": "0.1.2",
3636
"es6-shim": "0.35.0",
3737
"graphql": "^0.5.0",
38+
"lodash": "^4.11.1",
3839
"reflect-metadata": "0.1.2",
3940
"rxjs": "5.0.0-beta.2",
4041
"zone.js": "0.6.11"

src/apolloDecorator.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/// <reference path="../typings/main.d.ts" />
2+
3+
import ApolloClient from 'apollo-client';
4+
5+
import {
6+
isEqual,
7+
noop,
8+
forIn,
9+
} from 'lodash';
10+
11+
export declare interface ApolloOptionsQueries {
12+
context: any;
13+
};
14+
15+
export declare interface ApolloOptionsMutations {
16+
context: any;
17+
};
18+
19+
export declare interface ApolloOptions {
20+
client: ApolloClient;
21+
queries?(opts: ApolloOptionsQueries): any;
22+
mutations?(opts: ApolloOptionsMutations): any;
23+
};
24+
25+
export function Apollo({
26+
client,
27+
queries,
28+
mutations,
29+
}: ApolloOptions) {
30+
if (!(client instanceof ApolloClient)) {
31+
throw new Error('Client must be a ApolloClient instance');
32+
}
33+
34+
const { watchQuery } = client;
35+
36+
// noop by default
37+
queries = queries || noop;
38+
mutations = mutations || noop;
39+
40+
// holds latest values to track changes
41+
const lastQueryVariables = {};
42+
43+
return (sourceTarget: any) => {
44+
const target = sourceTarget;
45+
46+
const oldHooks = {};
47+
const hooks = {
48+
/**
49+
* Initialize the component
50+
* after Angular initializes the data-bound input properties.
51+
*/
52+
ngOnInit() {
53+
// use component's context
54+
const component = this;
55+
handleQueries(component, (key, { query, variables }) => {
56+
assign(component, key, { query, variables });
57+
});
58+
},
59+
/**
60+
* Detect and act upon changes that Angular can or won't detect on its own.
61+
* Called every change detection run.
62+
*/
63+
ngDoCheck() {
64+
// use component's context
65+
const component = this;
66+
handleQueries(component, (queryName, { query, variables }) => {
67+
// check if query needs to be rerun
68+
if (!equalVariablesOf(queryName, variables)) {
69+
assign(component, queryName, { query, variables });
70+
}
71+
});
72+
},
73+
};
74+
75+
// attach hooks
76+
forIn(hooks, (hook, name) => {
77+
wrapPrototype(name, hook);
78+
});
79+
80+
/**
81+
* Gets the result of the `queries` method
82+
* from decorator's options with component's context to compile variables.
83+
*
84+
* Then goes through all defined queries and calls a `touch` function.
85+
*
86+
* @param {any} component Component's context
87+
* @param {Function} touch Receives name and options
88+
*/
89+
function handleQueries(component: any, touch: Function) {
90+
forIn(queries(component), (opts: any, queryName: string) => {
91+
touch(queryName, opts);
92+
});
93+
}
94+
95+
/**
96+
* Assings WatchQueryHandle to component
97+
*
98+
* @param {any} component Component's context
99+
* @param {string} queryName Query's name
100+
* @param {Object} options Query's options
101+
*/
102+
function assign(component: any, queryName: string, { query, variables }) {
103+
// save variables so they can be used in futher comparasion
104+
lastQueryVariables[queryName] = variables;
105+
// assign to component's context
106+
component[queryName] = watchQuery({ query, variables });
107+
}
108+
109+
/**
110+
* Compares current variables with previous ones.
111+
*
112+
* @param {string} queryName Query's name
113+
* @param {any} variables current variables
114+
* @return {boolean} comparasion result
115+
*/
116+
function equalVariablesOf(queryName: string, variables: any): boolean {
117+
return isEqual(lastQueryVariables[queryName], variables);
118+
}
119+
120+
/**
121+
* Creates a new prototype method which is a wrapper function
122+
* that calls new function before old one.
123+
*
124+
* @param {string} name [description]
125+
* @param {Function} func [description]
126+
*/
127+
function wrapPrototype(name: string, func: Function) {
128+
oldHooks[name] = sourceTarget.prototype[name];
129+
// create a wrapper
130+
target.prototype[name] = function(...args) {
131+
// to call a new prototype method
132+
func.apply(this, args);
133+
134+
// call the old prototype method
135+
if (oldHooks[name]) {
136+
oldHooks[name].apply(this, args);
137+
}
138+
};
139+
}
140+
141+
// return decorated target
142+
return target;
143+
};
144+
}

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import {
22
ApolloQueryPipe,
33
} from './apolloQueryPipe';
44

5+
import {
6+
Apollo,
7+
} from './apolloDecorator';
8+
59
import {
610
Angular2Apollo,
711
defaultApolloClient,
@@ -12,6 +16,7 @@ export const APOLLO_PROVIDERS: any[] = [
1216
];
1317

1418
export {
19+
Apollo,
1520
ApolloQueryPipe,
1621
Angular2Apollo,
1722
defaultApolloClient

0 commit comments

Comments
 (0)