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
5 changes: 5 additions & 0 deletions .changeset/thin-otters-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'apollo-angular': patch
---

Let types flow naturally from `ObservableQuery`
151 changes: 65 additions & 86 deletions packages/apollo-angular/src/query-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {
ApolloQueryResult,
ObservableQuery,
OperationVariables,
SubscribeToMoreOptions,
TypedDocumentNode,
} from '@apollo/client/core';
import { NetworkStatus } from '@apollo/client/core';
Expand Down Expand Up @@ -43,99 +42,79 @@ function useInitialLoading<T, V extends OperationVariables>(obsQuery: Observable
export type QueryRefFromDocument<T extends TypedDocumentNode> =
T extends TypedDocumentNode<infer R, infer V> ? QueryRef<R, V & OperationVariables> : never;

export class QueryRef<TData, TVariables extends OperationVariables = EmptyObject> {
export class QueryRef<TData, TVariables extends OperationVariables = EmptyObject>
implements
Pick<
ObservableQuery<TData, TVariables>,
| 'queryId'
| 'options'
| 'variables'
| 'result'
| 'getCurrentResult'
| 'getLastResult'
| 'getLastError'
| 'resetLastResults'
| 'refetch'
| 'fetchMore'
| 'subscribeToMore'
| 'updateQuery'
| 'stopPolling'
| 'startPolling'
| 'setOptions'
| 'setVariables'
>
{
public readonly valueChanges: Observable<ApolloQueryResult<TData>>;
public readonly queryId: ObservableQuery<TData, TVariables>['queryId'];

// Types flow straight from ObservableQuery
public readonly queryId;
public readonly result;
public readonly getCurrentResult;
public readonly getLastResult;
public readonly getLastError;
public readonly resetLastResults;
public readonly refetch;
public readonly fetchMore;
public readonly subscribeToMore;
public readonly updateQuery;
public readonly stopPolling;
public readonly startPolling;
public readonly setOptions;
public readonly setVariables;

constructor(
private readonly obsQuery: ObservableQuery<TData, TVariables>,
private readonly query: ObservableQuery<TData, TVariables>,
ngZone: NgZone,
options: WatchQueryOptions<TVariables, TData>,
) {
const wrapped = wrapWithZone(from(fixObservable(this.obsQuery)), ngZone);
const wrapped = wrapWithZone(from(fixObservable(this.query)), ngZone);

this.valueChanges = options.useInitialLoading
? wrapped.pipe(useInitialLoading(this.obsQuery))
? wrapped.pipe(useInitialLoading(this.query))
: wrapped;
this.queryId = this.obsQuery.queryId;
}

// ObservableQuery's methods

public get options(): ObservableQuery<TData, TVariables>['options'] {
return this.obsQuery.options;
}

public get variables(): ObservableQuery<TData, TVariables>['variables'] {
return this.obsQuery.variables;
}

public result(): ReturnType<ObservableQuery<TData, TVariables>['result']> {
return this.obsQuery.result();
}

public getCurrentResult(): ReturnType<ObservableQuery<TData, TVariables>['getCurrentResult']> {
return this.obsQuery.getCurrentResult();
}

public getLastResult(): ReturnType<ObservableQuery<TData, TVariables>['getLastResult']> {
return this.obsQuery.getLastResult();
}

public getLastError(): ReturnType<ObservableQuery<TData, TVariables>['getLastError']> {
return this.obsQuery.getLastError();
}

public resetLastResults(): ReturnType<ObservableQuery<TData, TVariables>['resetLastResults']> {
return this.obsQuery.resetLastResults();
}

public refetch(
variables?: Parameters<ObservableQuery<TData, TVariables>['refetch']>[0],
): ReturnType<ObservableQuery<TData, TVariables>['refetch']> {
return this.obsQuery.refetch(variables);
}

public fetchMore<TFetchVars extends OperationVariables = TVariables>(
fetchMoreOptions: Parameters<ObservableQuery<TData, TFetchVars>['fetchMore']>[0],
): ReturnType<ObservableQuery<TData, TFetchVars>['fetchMore']> {
return this.obsQuery.fetchMore(fetchMoreOptions);
}

public subscribeToMore<
TSubscriptionData = TData,
TSubscriptionVariables extends OperationVariables = TVariables,
>(
options: SubscribeToMoreOptions<TData, TSubscriptionVariables, TSubscriptionData, TVariables>,
): ReturnType<ObservableQuery<TData, TVariables>['subscribeToMore']> {
return this.obsQuery.subscribeToMore(options);
}

public updateQuery(
mapFn: Parameters<ObservableQuery<TData, TVariables>['updateQuery']>[0],
): ReturnType<ObservableQuery<TData, TVariables>['updateQuery']> {
return this.obsQuery.updateQuery(mapFn);
}

public stopPolling(): ReturnType<ObservableQuery<TData, TVariables>['stopPolling']> {
return this.obsQuery.stopPolling();
}

public startPolling(
pollInterval: Parameters<ObservableQuery<TData, TVariables>['startPolling']>[0],
): ReturnType<ObservableQuery<TData, TVariables>['startPolling']> {
return this.obsQuery.startPolling(pollInterval);
}

public setOptions(
opts: Parameters<ObservableQuery<TData, TVariables>['setOptions']>[0],
): ReturnType<ObservableQuery<TData, TVariables>['setOptions']> {
return this.obsQuery.setOptions(opts);
}

public setVariables(
variables: Parameters<ObservableQuery<TData, TVariables>['setVariables']>[0],
): ReturnType<ObservableQuery<TData, TVariables>['setVariables']> {
return this.obsQuery.setVariables(variables);
this.queryId = this.query.queryId;

// ObservableQuery's methods
this.result = this.query.result.bind(this.query);
this.getCurrentResult = this.query.getCurrentResult.bind(this.query);
this.getLastResult = this.query.getLastResult.bind(this.query);
this.getLastError = this.query.getLastError.bind(this.query);
this.resetLastResults = this.query.resetLastResults.bind(this.query);
this.refetch = this.query.refetch.bind(this.query);
this.fetchMore = this.query.fetchMore.bind(this.query);
this.subscribeToMore = this.query.subscribeToMore.bind(this.query);
this.updateQuery = this.query.updateQuery.bind(this.query);
this.stopPolling = this.query.stopPolling.bind(this.query);
this.startPolling = this.query.startPolling.bind(this.query);
this.setOptions = this.query.setOptions.bind(this.query);
this.setVariables = this.query.setVariables.bind(this.query);
}

public get options() {
return this.query.options;
}

public get variables() {
return this.query.variables;
}
}
4 changes: 2 additions & 2 deletions packages/apollo-angular/tests/Apollo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ describe('Apollo', () => {
`,
};

client.watchQuery = jest.fn().mockReturnValue(new Observable());
const spy = jest.spyOn(client, 'watchQuery');
apollo.watchQuery(options);

expect(client.watchQuery).toBeCalledWith(options);
expect(spy).toBeCalledWith(options);
});

test('should be able to refetch', (done: jest.DoneCallback) => {
Expand Down
47 changes: 38 additions & 9 deletions packages/apollo-angular/tests/QueryRef.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ const createClient = (link: ApolloLink) =>
cache: new InMemoryCache(),
});

type Result = {
heroes: { name: string }[];
};

type Variables = {
foo?: number;
};

const heroesOperation = {
query: gql`
query: gql<Result, Variables>`
query allHeroes {
heroes {
name
Expand All @@ -40,8 +48,7 @@ const Batman = {
describe('QueryRef', () => {
let ngZone: NgZone;
let client: ApolloClient<any>;
let obsQuery: ObservableQuery<any>;
let queryRef: QueryRef<any>;
let obsQuery: ObservableQuery<Result, Variables>;

beforeEach(() => {
ngZone = { run: jest.fn(cb => cb()) } as any;
Expand All @@ -58,10 +65,14 @@ describe('QueryRef', () => {

client = createClient(mockedLink);
obsQuery = client.watchQuery(heroesOperation);
queryRef = new QueryRef<any>(obsQuery, ngZone, {} as any);
});

function createQueryRef(obsQuery: ObservableQuery<Result>): QueryRef<Result, Variables> {
return new QueryRef(obsQuery, ngZone, { query: heroesOperation.query });
}

test('should listen to changes', done => {
const queryRef = createQueryRef(obsQuery);
queryRef.valueChanges.subscribe({
next: result => {
expect(result.data).toBeDefined();
Expand All @@ -77,6 +88,7 @@ describe('QueryRef', () => {
const mockCallback = jest.fn();
obsQuery.refetch = mockCallback;

const queryRef = createQueryRef(obsQuery);
queryRef.refetch();

expect(mockCallback.mock.calls.length).toBe(1);
Expand All @@ -85,6 +97,7 @@ describe('QueryRef', () => {
test('should be able refetch and receive new results', done => {
let calls = 0;

const queryRef = createQueryRef(obsQuery);
queryRef.valueChanges.subscribe({
next: result => {
calls++;
Expand All @@ -110,6 +123,7 @@ describe('QueryRef', () => {

test('should be able refetch and receive new results after using rxjs operator', done => {
let calls = 0;
const queryRef = createQueryRef(obsQuery);
const obs = queryRef.valueChanges;

obs.pipe(map(result => result.data)).subscribe({
Expand Down Expand Up @@ -139,9 +153,10 @@ describe('QueryRef', () => {

test('should be able to call updateQuery()', () => {
const mockCallback = jest.fn();
const mapFn = () => ({});
const mapFn = () => undefined;
obsQuery.updateQuery = mockCallback;

const queryRef = createQueryRef(obsQuery);
queryRef.updateQuery(mapFn);

expect(mockCallback.mock.calls.length).toBe(1);
Expand All @@ -152,6 +167,7 @@ describe('QueryRef', () => {
const mockCallback = jest.fn();
obsQuery.result = mockCallback.mockReturnValue('expected');

const queryRef = createQueryRef(obsQuery);
const result = queryRef.result();

expect(result).toBe('expected');
Expand All @@ -160,6 +176,7 @@ describe('QueryRef', () => {

test('should be able to call getCurrentResult() and get updated results', done => {
let calls = 0;
const queryRef = createQueryRef(obsQuery);
const obs = queryRef.valueChanges;

obs.pipe(map(result => result.data)).subscribe({
Expand Down Expand Up @@ -189,6 +206,7 @@ describe('QueryRef', () => {
const mockCallback = jest.fn();
obsQuery.getLastResult = mockCallback.mockReturnValue('expected');

const queryRef = createQueryRef(obsQuery);
const result = queryRef.getLastResult();

expect(result).toBe('expected');
Expand All @@ -199,6 +217,7 @@ describe('QueryRef', () => {
const mockCallback = jest.fn();
obsQuery.getLastError = mockCallback.mockReturnValue('expected');

const queryRef = createQueryRef(obsQuery);
const result = queryRef.getLastError();

expect(result).toBe('expected');
Expand All @@ -209,6 +228,7 @@ describe('QueryRef', () => {
const mockCallback = jest.fn();
obsQuery.resetLastResults = mockCallback.mockReturnValue('expected');

const queryRef = createQueryRef(obsQuery);
const result = queryRef.resetLastResults();

expect(result).toBe('expected');
Expand All @@ -217,10 +237,11 @@ describe('QueryRef', () => {

test('should be able to call fetchMore()', () => {
const mockCallback = jest.fn();
const opts = { foo: 1 };
const opts = { variables: { foo: 1 } };
obsQuery.fetchMore = mockCallback.mockReturnValue('expected');

const result = queryRef.fetchMore(opts as any);
const queryRef = createQueryRef(obsQuery);
const result = queryRef.fetchMore(opts);

expect(result).toBe('expected');
expect(mockCallback.mock.calls.length).toBe(1);
Expand All @@ -229,10 +250,11 @@ describe('QueryRef', () => {

test('should be able to call subscribeToMore()', () => {
const mockCallback = jest.fn();
const opts = { foo: 1 };
const opts = { document: heroesOperation.query };
obsQuery.subscribeToMore = mockCallback;

queryRef.subscribeToMore(opts as any);
const queryRef = createQueryRef(obsQuery);
queryRef.subscribeToMore(opts);

expect(mockCallback.mock.calls.length).toBe(1);
expect(mockCallback.mock.calls[0][0]).toBe(opts);
Expand All @@ -242,6 +264,7 @@ describe('QueryRef', () => {
const mockCallback = jest.fn();
obsQuery.stopPolling = mockCallback;

const queryRef = createQueryRef(obsQuery);
queryRef.stopPolling();

expect(mockCallback.mock.calls.length).toBe(1);
Expand All @@ -251,6 +274,7 @@ describe('QueryRef', () => {
const mockCallback = jest.fn();
obsQuery.startPolling = mockCallback;

const queryRef = createQueryRef(obsQuery);
queryRef.startPolling(3000);

expect(mockCallback.mock.calls.length).toBe(1);
Expand All @@ -262,6 +286,7 @@ describe('QueryRef', () => {
const opts = {};
obsQuery.setOptions = mockCallback.mockReturnValue('expected');

const queryRef = createQueryRef(obsQuery);
const result = queryRef.setOptions(opts);

expect(result).toBe('expected');
Expand All @@ -274,6 +299,7 @@ describe('QueryRef', () => {
const variables = {};
obsQuery.setVariables = mockCallback.mockReturnValue('expected');

const queryRef = createQueryRef(obsQuery);
const result = queryRef.setVariables(variables);

expect(result).toBe('expected');
Expand All @@ -282,6 +308,7 @@ describe('QueryRef', () => {
});

test('should handle multiple subscribers', done => {
const queryRef = createQueryRef(obsQuery);
const obsFirst = queryRef.valueChanges;
const obsSecond = queryRef.valueChanges;

Expand Down Expand Up @@ -338,6 +365,7 @@ describe('QueryRef', () => {
});

test('should unsubscribe', done => {
const queryRef = createQueryRef(obsQuery);
const obs = queryRef.valueChanges;
const id = queryRef.queryId;

Expand All @@ -356,6 +384,7 @@ describe('QueryRef', () => {

test('should unsubscribe based on rxjs operators', done => {
const gate = new Subject<void>();
const queryRef = createQueryRef(obsQuery);
const obs = queryRef.valueChanges.pipe(takeUntil(gate));
const id = queryRef.queryId;

Expand Down
Loading