Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### vNEXT

- Added `ApolloModule` (with RC5 of Angular2 comes NgModules) ([PR #63](https://github.com/apollostack/angular2-apollo/pull/63))
- Added ability to use query variables as observables. With this, the query can be automatically re-run when those obserables emit new values. ([PR #64]((https://github.com/apollostack/angular2-apollo/pull/64)))

### v0.4.2

Expand Down
2 changes: 1 addition & 1 deletion examples/hello-world/client/imports/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ <h2>Add new</h2>

<h2>List</h2>

<input type="search" placeholder="Type name..." [(ngModel)]="nameFilter" />
<input type="search" placeholder="Type name..." [formControl]="nameControl" />

<ul>
<li *ngFor="let user of data | async | apolloQuery: 'users'">
Expand Down
29 changes: 21 additions & 8 deletions examples/hello-world/client/imports/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, AfterViewInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Angular2Apollo, ApolloQueryPipe, ApolloQueryObservable } from 'angular2-apollo';
import { ApolloQueryResult } from 'apollo-client';
import { Subject } from 'rxjs/Subject';

import { User } from './user.interface';

import gql from 'graphql-tag';

import 'rxjs/add/operator/debounceTime';

import template from './app.component.html';

interface Data {
Expand All @@ -17,15 +21,16 @@ interface Data {
template,
pipes: [ApolloQueryPipe],
})
export class AppComponent implements OnInit {
data: ApolloQueryObservable<Data>;
firstName: string;
lastName: string;
nameFilter: string;
export class AppComponent implements OnInit, AfterViewInit {
public data: ApolloQueryObservable<Data>;
public firstName: string;
public lastName: string;
public nameControl = new FormControl();
public nameFilter: Subject<string> = new Subject<string>();

constructor(private angular2Apollo: Angular2Apollo) {}

ngOnInit() {
public ngOnInit() {
this.data = this.angular2Apollo.watchQuery({
query: gql`
query getUsers($name: String) {
Expand All @@ -43,9 +48,17 @@ export class AppComponent implements OnInit {
name: this.nameFilter,
},
});

this.nameControl.valueChanges.debounceTime(300).subscribe(name => {
this.nameFilter.next(name);
});
}

public ngAfterViewInit() {
this.nameFilter.next(null);
}

newUser(firstName: string) {
public newUser(firstName: string) {
this.angular2Apollo.mutate({
mutation: gql`
mutation addUser(
Expand Down
3 changes: 2 additions & 1 deletion examples/hello-world/client/imports/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ApolloModule } from 'angular2-apollo';

import { AppComponent } from './app.component';
Expand All @@ -11,6 +11,7 @@ import { client } from './client';
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
ApolloModule.withClient(client),
],
bootstrap: [ AppComponent ],
Expand Down
5 changes: 5 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ declare module 'lodash.assign' {
import main = require('~lodash/index');
export = main.assign;
}

declare module 'lodash.omit' {
import main = require('~lodash/index');
export = main.omit;
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"dependencies": {
"lodash.assign": "^4.0.9",
"lodash.forin": "^4.2.0",
"lodash.isequal": "^4.2.0"
"lodash.isequal": "^4.2.0",
"lodash.omit": "^4.4.1"
},
"devDependencies": {
"@angular/common": "^2.0.0-rc.5",
Expand Down
38 changes: 34 additions & 4 deletions src/angular2Apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,23 @@ import {
Inject,
} from '@angular/core';

import 'rxjs/add/operator/switchMap';

import assign = require('lodash.assign');
import omit = require('lodash.omit');

import {
ApolloQueryObservable,
} from './apolloQueryObservable';

import {
ObservableQueryRef,
} from './utils/observableQuery';

import {
observeVariables,
} from './utils/observeVariables';

export const angularApolloClient = new OpaqueToken('AngularApolloClient');
export const defaultApolloClient = (client: ApolloClient): Provider => {
return provide(angularApolloClient, {
Expand All @@ -23,12 +36,29 @@ export const defaultApolloClient = (client: ApolloClient): Provider => {
export class Angular2Apollo {
constructor(
@Inject(angularApolloClient) private client: any
) {

}
) {}

public watchQuery(options): ApolloQueryObservable<any> {
return new ApolloQueryObservable(this.client.watchQuery(options));
const apolloRef = new ObservableQueryRef();
if (typeof options.variables === 'object') {
const varObs = observeVariables(options.variables);

return new ApolloQueryObservable(apolloRef, subscriber => {
const sub = varObs.switchMap(newVariables => {
const cleanOptions = omit(options, 'variables');
const newOptions = assign(cleanOptions, { variables: newVariables });

apolloRef.apollo = this.client.watchQuery(newOptions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kamilkisiela would it be better to do a refetch with the new variables instead of creating a new query here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbaxleyiii I don't know if it would work with switchMap, I'll take a look on this.


return apolloRef.apollo;
}).subscribe(subscriber);

return () => sub.unsubscribe();
});
}

apolloRef.apollo = this.client.watchQuery(options);
return new ApolloQueryObservable(apolloRef);
}

public query(options) {
Expand Down
20 changes: 6 additions & 14 deletions src/apolloQueryObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/Subscriber';
import { Subscription } from 'rxjs/Subscription';
import { Operator } from 'rxjs/Operator';
import { $$observable } from 'rxjs/symbol/observable';
import { ApolloQueryResult } from 'apollo-client';

import { FetchMoreOptions } from 'apollo-client/ObservableQuery';
import { FetchMoreQueryOptions } from 'apollo-client/watchQueryOptions';
import { ObservableQueryRef, IObservableQuery } from './utils/observableQuery';

export class ApolloQueryObservable<T> extends Observable<T> {
constructor(public apollo: any, subscribe?: <R>(subscriber: Subscriber<R>) => Subscription | Function | void) {
export class ApolloQueryObservable<T> extends Observable<T> implements IObservableQuery {
constructor(public apollo: ObservableQueryRef, subscribe?: <R>(subscriber: Subscriber<R>) => Subscription | Function | void) {
super(subscribe);
}

Expand All @@ -23,7 +22,7 @@ export class ApolloQueryObservable<T> extends Observable<T> {

// apollo-specific methods

public refetch(variables?: any): Promise<any> {
public refetch(variables?: any): Promise<ApolloQueryResult> {
return this.apollo.refetch(variables);
}

Expand All @@ -35,14 +34,7 @@ export class ApolloQueryObservable<T> extends Observable<T> {
return this.apollo.startPolling(p);
}

public fetchMore(options: FetchMoreQueryOptions & FetchMoreOptions): Promise<any> {
public fetchMore(options: any): Promise<any> {
return this.apollo.fetchMore(options);
}

// where magic happens

protected _subscribe(subscriber: Subscriber<T>) {
const apollo = this.apollo;
return apollo[$$observable]().subscribe(subscriber);
}
}
34 changes: 34 additions & 0 deletions src/utils/observableQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
ObservableQuery,
} from 'apollo-client/ObservableQuery';

import {
ApolloQueryResult,
} from 'apollo-client';

export interface IObservableQuery {
refetch: (variables?: any) => Promise<ApolloQueryResult>;
fetchMore: (options: any) => Promise<any>;
stopPolling: () => void;
startPolling: (p: number) => void;
}

export class ObservableQueryRef implements IObservableQuery {
public apollo: ObservableQuery;

public refetch(variables?: any): Promise<ApolloQueryResult> {
return this.apollo.refetch(variables);
}

public stopPolling(): void {
return this.apollo.stopPolling();
}

public startPolling(p: number): void {
return this.apollo.startPolling(p);
}

public fetchMore(options: any): Promise<any> {
return this.apollo.fetchMore(options);
}
}
41 changes: 41 additions & 0 deletions src/utils/observeVariables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';

import 'rxjs/add/observable/combineLatest';

export function observeVariables(variables?: Object): Observable<Object> {
const keys = Object.keys(variables);

return Observable.create((observer: Observer<any>) => {
Observable.combineLatest(mapVariablesToObservables(variables))
.subscribe((values) => {
const resultVariables = {};

values.forEach((value, i) => {
const key = keys[i];
resultVariables[key] = value;
});

observer.next(resultVariables);
});
});
};

function mapVariablesToObservables(variables?: Object) {
return Object.keys(variables)
.map(key => getVariableToObservable(variables[key]));
}

function getVariableToObservable(variable: any | Observable<any>) {
if (variable instanceof Observable) {
return variable;
} else if (typeof variable !== 'undefined') {
return new Observable<any>(subscriber => {
subscriber.next(variable);
});
} else {
return new Observable<any>(subscriber => {
subscriber.next(undefined);
});
}
}
11 changes: 2 additions & 9 deletions tests/_mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import {
print,
} from 'graphql-tag/printer';

// Pass in multiple mocked responses, so that you can test flows that end up
// making multiple queries to the server
export default function mockNetworkInterface(
...mockedResponses: MockedResponse[]
): NetworkInterface {
Expand All @@ -25,7 +23,7 @@ export default function mockNetworkInterface(

export function mockBatchedNetworkInterface(
...mockedResponses: MockedResponse[]
): NetworkInterface {
): BatchedNetworkInterface {
return new MockBatchedNetworkInterface(...mockedResponses);
}

Expand Down Expand Up @@ -71,9 +69,8 @@ export class MockNetworkInterface implements NetworkInterface {

const key = requestToKey(parsedRequest);
const responses = this.mockedResponsesByKey[key];

if (!responses || responses.length === 0) {
throw new Error('No more mocked responses for the query: ' + print(request.query));
throw new Error(`No more mocked responses for the query: ${print(request.query)}, variables: ${JSON.stringify(request.variables)}`);
}

const { result, error, delay } = responses.shift();
Expand All @@ -92,22 +89,18 @@ export class MockNetworkInterface implements NetworkInterface {
});
}
}

export class MockBatchedNetworkInterface
extends MockNetworkInterface implements BatchedNetworkInterface {
public batchQuery(requests: Request[]): Promise<GraphQLResult[]> {
const resultPromises: Promise<GraphQLResult>[] = [];
requests.forEach((request) => {
resultPromises.push(this.query(request));
});

return Promise.all(resultPromises);
}
}

function requestToKey(request: ParsedRequest): string {
const queryString = request.query && print(request.query);

return JSON.stringify({
variables: request.variables,
debugName: request.debugName,
Expand Down
Loading