Created
March 21, 2019 21:47
-
-
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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