Wednesday 10 April 2019

Service Locator pattern in SPFx: Using nested scopes to work with multiple components

In the previous post, we saw how SharePoint Framework code can be decoupled by using the Service Locator pattern and Service Scopes. In short, we are able to register instances of our services on the "global" service scope and then consume those instances from any part of our code.

We can also consume instances of default services registered on the global service scope e.g. MSGraphClient, AadHttpClient, SPHttpClient etc. These registrations are done by the SharePoint Framework and all we have to do is hook into the global service scope to fetch their instances.

Now, when we register our custom service on the global service scope, all components on the SharePoint page share a single instance of the service. By components, I mean SPFx web parts and extensions. In this post, to keep things simple, I will only talk about web parts but the concepts apply to extensions as well.

When the first webpart on the page makes a call to the service scope, an instance of the custom service is created and returned. Any further calls made by the same or different webparts on the page will return the same instance of the service. Now this behaviour can be either good or bad depending on your requirements.

If you want different webparts on the page to have their own instances of the custom service, let's see how to achieve this:

Let's consider a simple custom service:

The service contains a private variable count which is initialised to 0. The increaseAndReturnCount function increases the count by 1 and then returns the value. Not the best code ever written but fits our current purpose.

Now, let's register and consume an instance of our custom service from an SPFx webpart:

If you are wondering where do we register this service as we only seem to be calling the consume function. The consume function does the registration for us by creating a default instance of the service, registering it on the service scope and then returning the same instance.

When we register our service on the global service scope returned by this.context.serviceScope, all webparts and SPFx components will share the same service instance:

Although multiple webparts are added on the page, they all share the same service instance and the same count variable keeps on increasing. OK, technically I am adding the same webpart multiple times on the page but the behaviour is the same even for different webparts calling the same service.

Now, to get around this issue, we only need to make a simple change to our code. We don't need to change any of the service code, we just need to change the way in which the service instance is created and returned:

By creating a new service scope as a child of the global service scope, we get a new service scope which is isolated to the current web part. This way, each web part will get it's own service scope to register and consume service instances.

And as a result, we get a new instance of the counter service for each web part where the counter variable is initialised to 0:

This way, we are able to create and maintain isolated service scopes per component on our page.

Hope you found this post useful!

The code is in GitHub if you want to take a look:

No comments: