Do I really need N number of Intersection Observers in my app?
While initializing an IntersectionObserver instance in my NextJs component, it was creating a situation where there were N number of instances per N number of components rendered. Although it should not be affecting the performance too much, unless you are rendering too many components, maybe a hundred, or a thousand? Found this article that talks about the same performance issue. It explores the IntersectionObserver API's Performance. Although, the article deduced that the performance bottleneck was more due to Angular's change detection. But still there are "some built-in performance advantages" by using single instance, as the actual author of the API, pointed out in their comments, here. Now to tackle this problem, the solution would be fairly easy. We can create a single instance in a parent container or a different file, to observe all the child components. That way there would be no need of creating a per component thing. We might also want our callbacks to be different for any unique component. Well, we can do it. Put the logic in that parent container? Possible, but I'm not a fan of this approach. I want to build components, that are self-sufficient and also don't spin another observer. What I felt here, that was missing, is that there is a need of some kind of systematic configuration. I can start by thinking, how a flexible abstraction over the regular API can be done. It appears I want to store my instances at a single place. I probably need a map too, so that I can identify the instance. Let's name it XObservers. It would have an entry whenever an observer registers to it. Let's call it XObserverEntry. export class XObserver { static xObservers = new Map(); } Now, what would be inside XObserverEntry? It's surely going to contain an IntersectionObserver object, and information about what components are subscribed to it? That could be an array of subscribers. I used a Map here again, because I needed to identify the subscribers too, for the sake of implementation. type XObserverEntry = { observer: IntersectionObserver; subscribers: Map; } Another annoying abstraction I poured into it, is XObserverSubscription. type XObserverSubscription = { callback?: XObserverCallback; }; Which only contains a callback function. But a 'good' callback function, XObserverCallback yeah, another abstraction. Deal with it. type XObserverCallback = (entry: IntersectionObserverEntry) => void; But look! it gives you one IntersectionObserverEntry, a single object, not a dumb array of it. But which entry exactly? Well, the element that subscribed to it. The element for which an object of XObserverSubscription was created in the subscribers Map. Okay, that is the design part. You can read the implementation in code. Because I cleaned the code a bit, fixed some bugs, made it a bit tolerant against small in-consistencies, and the NPM package is ready to serve. @msc24x/xobserver my not so wanted, revolutionary wrapper around the regular IntersectionObserver. (can I get some VC money now?) It worked for me, to provide convenience? of course, yes. Performance gain? nope (I don't think it made it worse either). And in vanilla JavaScript, I don't think you might even need it, unless you want it. Thats how I think about it.
![Do I really need N number of Intersection Observers in my app?](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhre6v0j84l04pfoglu8j.gif)
While initializing an IntersectionObserver
instance in my NextJs component, it was creating a situation where there were N
number of instances per N
number of components rendered.
Although it should not be affecting the performance too much, unless you are rendering too many components, maybe a hundred, or a thousand?
Found this article that talks about the same performance issue. It explores the IntersectionObserver API's Performance.
Although, the article deduced that the performance bottleneck was more due to Angular's change detection. But still there are "some built-in performance advantages" by using single instance, as the actual author of the API, pointed out in their comments, here.
Now to tackle this problem, the solution would be fairly easy. We can create a single instance in a parent container or a different file, to observe all the child components. That way there would be no need of creating a per component thing. We might also want our callbacks to be different for any unique component. Well, we can do it. Put the logic in that parent container? Possible, but I'm not a fan of this approach. I want to build components, that are self-sufficient and also don't spin another observer. What I felt here, that was missing, is that there is a need of some kind of systematic configuration.
I can start by thinking, how a flexible abstraction over the regular API can be done. It appears I want to store my instances at a single place. I probably need a map too, so that I can identify the instance. Let's name it XObservers. It would have an entry whenever an observer registers to it. Let's call it XObserverEntry
.
export class XObserver {
static xObservers = new Map<string, XObserverEntry>();
}
Now, what would be inside XObserverEntry
? It's surely going to contain an IntersectionObserver object, and information about what components are subscribed to it? That could be an array of subscribers. I used a Map here again, because I needed to identify the subscribers too, for the sake of implementation.
type XObserverEntry = {
observer: IntersectionObserver;
subscribers: Map<string, XObserverSubscription>;
}
Another annoying abstraction I poured into it, is XObserverSubscription
.
type XObserverSubscription = {
callback?: XObserverCallback;
};
Which only contains a callback function. But a 'good' callback function, XObserverCallback
yeah, another abstraction. Deal with it.
type XObserverCallback = (entry: IntersectionObserverEntry) => void;
But look! it gives you one IntersectionObserverEntry
, a single object, not a dumb array of it. But which entry exactly? Well, the element that subscribed to it. The element for which an object of XObserverSubscription
was created in the subscribers
Map.
Okay, that is the design part. You can read the implementation in code. Because I cleaned the code a bit, fixed some bugs, made it a bit tolerant against small in-consistencies, and the NPM package is ready to serve.
@msc24x/xobserver my not so wanted, revolutionary wrapper around the regular IntersectionObserver
. (can I get some VC money now?)
It worked for me, to provide convenience? of course, yes. Performance gain? nope (I don't think it made it worse either). And in vanilla JavaScript, I don't think you might even need it, unless you want it. Thats how I think about it.