Recently, I was hanging out in the Shopify Partner Community Slack in the #appdev channel and noticed a developer asking an interesting question:

Many plugin developers may find themselves in a similar situation, needing a way to listen to changes on the window.Shopify
and window.ShopifyAnalytics
objects on a Shopify storefront.
Unfortunately, these global window objects provided by Shopify are just plain old javascript objects. There isn’t an API interface to subscribe to changes on the object.
However, using old school polling techniques, we can easily write some simple code to check for changes on this object every n
milliseconds:
const checkShopifyChanges = () => {
// check if specific field changed on
// window.Shopify or window.ShopifyAnalytics
}
const interval = setInterval(checkShopifyChanges, 1000)
Pretty simple, just a basic JS Interval. However, the original question was specifically about writing a listener.
It’s worth mentioning at this point that Shopify does provide some window events that you can subscribe to. As seen in this post in the community forums, Shopify publishes a variant:changed
and a product:added
event that you can easily subscribe to like this:
document.addEventListener('variant:changed', (event) => {
// handle variant selection
})
document.addEventListener('product:added', (event) => {
// handle addToCart event
})
This is helpful if all you need to do is respond to a variant selection or an addToCart event. However, the Shopify and ShopifyAnalytics objects hold a host of other information that might be useful to the function of your application.
So how can we bridge the gap and use the Observer pattern to listen to changes on these window objects?
That’s where an Observer library like RxJS comes to the rescue! While this library provides several data structures that allow developers to apply the Observer pattern in their applications, for our use case, we are interested in the BehaviorSubject class, which functions much like a NodeJS EventEmitter.
In essence, each BehaviorSubject instance stores a value, and provides a next(value: any)
and subscribe((value: any) => void)
function to publish changes and subscribe to changes on the BehaviorSubject.
We can leverage both the BehaviorSubject class and traditional polling to solve this problem. At a high level, we will instantiate a BehaviorSubject for each Shopify object we care about and we will setup an interval that checks for changes on the window.Shopify
and window.ShopifyAnalytics
objects. If there is a diff, it publishes a change to the corresponding BehaviorSubject.
First things first, let’s import the RxJS library into our code:
import { BehaviorSubject } from 'rxjs'
For the sake of this tutorial, let’s assume we’re writing our Shopify script with ES6, able to install npm packages, and using webpack or some other bundler to build the actual widget code that will be loaded onto the storefront. (If you are writing Shopify scripts in vanilla JS, you are at a serious disadvantage excluding yourself from the npm package ecosystem, looking at you jQuery developers 🤨).
Next, let’s create two new BehaviorSubject objects, one for window.Shopify
and one for window.ShopifyAnalytics
:
const shopifySubject = new BehaviorSubject(window.Shopify)
const shopifyAnalyticsSubject = new BehaviorSubject(window.ShopifyAnalytics)
Now, let’s setup a polling interval near where we initialize these BehaviorSubjects to watch for changes on the Shopify objects:
const watchShopify = () => {
const serializedCurrentShopify = JSON.stringify(window.Shopify)
const serializedCurrentShopifyAnalytics = JSON.stringify(window.ShopifyAnalytics)
const serializedShopify = JSON.stringify(shopifySubject.value)
const serializedShopifyAnalytics = JSON.stringify(shopifyAnalyticsSubject.value)
if (serializedCurrentShopify !== serializedShopify) {
shopifySubject.next(window.Shopify)
}
if (serializedCurrentShopifyAnalytics !== serializedShopifyAnalytics) {
shopifyAnalyticsSubject.next(window.ShopifyAnalytics)
}
}
const watchShopifyInterval = setInterval(watchShopify, 1000)
The above code will check every 1s if the Shopify objects have changed at all. If they have, we will use the BehaviorSubject#next function to publish the new version of that object.
Pretty neat, huh? A single JS Interval that does the quick diff check and is written once early in your widget initialization logic. From there, the BehaviorSubject allows you to add as many event listeners as you want using the BehaviorSubject#subscribe function. While you’ll need to ensure you’re passing around the same BehaviorSubject reference throughout your code, you can now subscribe to changes on that object anywhere it is necessary:
shopifySubject.subscribe((updatedShopify) => {
// perform some action
})
shopifyAnalyticsSubject.subscribe((updatedShopifyAnalytics) => {
// perform some action
})
Clean and efficient.
While this is a perfect solution for the original question, the vigilant among you will notice: this strategy could be useful in any situation where you want to subscribe to a change on any window object outside the control of your code.
You could also tweak the polling interval if 1s is too short or too frequent for your use case.
Cheers!