Apollo Client Best Practices
Introduction
I recently ran into a situation where I had some Observable
code that used a few bad practices and I decided to refactor. Here are my lessons-learned about Observables
and specifically about Apollo Client’s query
and watchQuery
methods.
The Refactor
Our example is the WidgetService
that is responsible for serving lists of Widgets
to the application and providing an interface for modifying them.
I’ve made comments inline to describe the intended behavior or bad practices.
Our code looks something like this:
So, we have some things that could be better (code wise) but we also have some non-optimal behavior. Lets implement better practices and add a couple nice features:
- Cache the widgets result and redistribute it to any other subscribers in our app without refetching pre-maturely. (Our app can cache this result as it will only change via user-interaction. Not all data requests can make this assumption.)
- Enable manual refetches of the data when we know something has changed; without forcing the downstream subscribers to re-subscribe.
Now we have clean definition of how/when `widgets$` will fire and how its pipeline works.
`query
` vs. `watchQuery`
Apollo Client has two different methods for executing queries against a GraphQL endpoint: query
and watchQuery
. The Apollo docs don’t really cover query
much at all and I’ve found using the two to be a bit confusing.
query
acts exactly like other RxJS based http clients: it will fire an http request whenever the query Observable is subscribed to and it will complete immediately. So, you could construct a poller doing something like this:
This will do what you want but the downside of the query
method is that it ignores Apollo Client’s cache. So if the cache gets updated elsewhere in the app and it affects this part of the graph, any subscribers to this poller will not get updated. If we want to be able to take advantage of the cache we need to use watchQuery
. So lets swap it out:
Functionally, this will work except that if you watch your dev tools you’ll see extraneous http requests going out. And your subscribes will get fired just as many extra times. Why does this happen? It’s because watchQuery().valueChanges
is an observable that DOES NOT COMPLETE after the original http request is finished because it monitors the cache. Because of this, every time the timer fires we will get another incomplete observable that is subscribed to in this pipeline.
Fortunately, watchQuery
has other mechanisms for solving the polling problem:
pollInterval
There is a pollInterval
configuration option that will automatically re-issue the query request at the specified interval.
refetch
The refetch
method on the QueryRef
class allows you to manually cause a refetch. Using that you can contract whatever sort of complicated polling/refetch strategy that you want.
Conclusion
Thanks for reading. Please comment if you have any questions or suggestions. Happy to hear from you.