Apollo Client Best Practices

Jonathan Samples
2 min readFeb 25, 2021

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:

  1. 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.)
  2. 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:

Poller using `query`

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:

Naive watchQuery poller

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.

--

--