Skip to content

Instantly share code, notes, and snippets.

@KeithGillette
Created March 21, 2019 21:47
Show Gist options
  • Save KeithGillette/0dfe7f8008e8854aa455adfe7d78c3af to your computer and use it in GitHub Desktop.
Save KeithGillette/0dfe7f8008e8854aa455adfe7d78c3af to your computer and use it in GitHub Desktop.
Apollo-Angular Monkey Patch Allowing Watched Queries Updates to be triggered by mutations <https://github.com/apollographql/apollo-feature-requests/issues/97>
import ApolloClient, { ApolloClientOptions, ApolloQueryResult, MutationOptions, MutationUpdaterFn, ObservableQuery, OperationVariables, WatchQueryOptions } from 'apollo-client';
import { DocumentNode } from 'graphql';
import { FetchResult } from 'apollo-link';
import { DataProxy } from 'apollo-cache';
import { QueryRef } from 'apollo-angular';
import { R } from 'apollo-angular/types';
interface IMutationUpdateEvent<T = any> {
mutation: DocumentNode;
dataProxy: DataProxy;
mutationResult: FetchResult<T>;
}
type IMutationWatchFunction = (mutationUpdateEvent: IMutationUpdateEvent) => void;
/* Merge "monkey-patched" methods into type definitions */
declare module 'apollo-angular/QueryRef' {
interface QueryRef<T, V = R> {
updateQueryOnMutation: (mutation: DocumentNode, queryUpdateFunction: MutationUpdaterFn, updateCacheAfterUnsubscribe?: boolean) => QueryRef<T, V>;
}
}
declare module 'apollo-client/core/ObservableQuery' {
interface ObservableQuery<TData = any, TVariables = OperationVariables> {
/** Private */
load: (variables: TVariables) => Promise<ApolloQueryResult<TData>>;
/** Private */
__mutationWatches: IMutationWatchFunction[];
}
}
const MutationEventsKey = Symbol('MutationSubscription');
ObservableQuery.prototype.__mutationWatches = [];
QueryRef.prototype.updateQueryOnMutation = function updateQueryOnMutation<T, V = R>(mutation: DocumentNode, queryUpdateFunction: MutationUpdaterFn, updateCacheAfterUnsubscribe: boolean = false): QueryRef<T, V> {
this.obsQuery.__mutationWatches.push(this.obsQuery[MutationEventsKey].on(mutation, ({dataProxy, mutationResult}: IMutationUpdateEvent) => {
queryUpdateFunction(dataProxy, mutationResult);
},
updateCacheAfterUnsubscribe)
);
return this;
};
// @ts-ignore
ObservableQuery.prototype.tearDownQuery = ((_super: Function): Function => {
return function tearDownQuery(...args: any[]): void {
this.__mutationWatches.forEach((watchRemover: () => void) => watchRemover());
this.__mutationWatches.length = 0;
return _super.apply(this, args);
};
// @ts-ignore
})(ObservableQuery.prototype.tearDownQuery);
ObservableQuery.prototype.load = function load<TData>(variables: OperationVariables): Promise<ApolloQueryResult<TData>> {
const result = this.setVariables(variables, true);
if (!this.observers.length) {
this.subscribe();
}
return result;
};
class MutationWatchEvents {
private mutationMap = new Map<DocumentNode, IMutationWatchFunction[]>();
public emit(mutation: DocumentNode, mutationUpdateEvent: IMutationUpdateEvent): void {
const mutationListeners = this.mutationMap.get(mutation) || [];
mutationListeners.slice(0).forEach((mutationWatchFunction: IMutationWatchFunction) => mutationWatchFunction(mutationUpdateEvent));
}
public on(mutation: DocumentNode, mutationWatchFunction: IMutationWatchFunction, updateCacheAfterUnsubscribe: boolean): () => void {
const mutationListeners = this.mutationMap.get(mutation) || [];
let isAttached = true;
mutationListeners.push(mutationWatchFunction);
this.mutationMap.set(mutation, mutationListeners);
return (): void => {
if (!updateCacheAfterUnsubscribe && isAttached) { // WARNING: Keeping listeners alive after query unsubscription may cause a memory leak
isAttached = false;
mutationListeners.splice(mutationListeners.indexOf(mutationWatchFunction), 1);
}
};
}
}
export class ApolloClientWithMutationSubscriptions<TCacheShape> extends ApolloClient<TCacheShape> {
private mutationWatchEvents: MutationWatchEvents = new MutationWatchEvents();
constructor(options: ApolloClientOptions<TCacheShape>) {
super(options);
}
public mutate<T = any, TVariables = OperationVariables>(options: MutationOptions<T, TVariables>): Promise<FetchResult<T>> {
return super.mutate({
update: (dataProxy: DataProxy, mutationResult: FetchResult<T>): void => {
this.mutationWatchEvents.emit(options.mutation, {
mutation: options.mutation,
dataProxy: dataProxy,
mutationResult: mutationResult
});
},
...options,
});
}
public watchQuery<T = any, TVariables = OperationVariables>(options: WatchQueryOptions<TVariables>): ObservableQuery<T, TVariables> {
const watchQuery = super.watchQuery(options);
watchQuery[MutationEventsKey] = this.mutationWatchEvents;
return watchQuery;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment