Hi SharePoint devs !
In this post, I will tackle something that you’d better keep in mind if, like me, you prefer to organize your code in layers and services.
You may have read my post a few months ago about Dependency Injection in SPFx. This one explained quite clearly how to use the SPFx ServiceScope class but didn’t really focused on an important part: Root scope and Child scope.
ServiceScope ? What’s that again ?
The ServiceScope class is acting as a container for service instances, it allows you to use the preconfigured instances of system services. This mean being more efficient rather than reinstantiate a new service everywhere is needed. It also allows to define your own services.
ServiceScope is also really handy if you want to deal with dependency injection concerns. It allows, for example, using different implementations of a service contract according to a specific context. (TEST or PROD context, different data providers, …)
The ServiceScope pattern built by SPFx engineers enforce a service contract to always have a default implementation. In the code exhibit below, I define a ListService that retrieves basic information about a SharePoint list
And that’s all we need to do to have to our custom service available in our solution. Specifically, the line 53 where we create the service key. This instruction creates a unique identifier for our service and make sure the ServiceScope will know the default implementation for our service.
With the code above, we implemented a default implementation that will be available in the ServiceScope. In the WebPart code, to get an instance of our service, you can use
let service = this.context.serviceScope.consume(ListService.serviceKey);
But we need to consider one thing, the this.context.serviceScope is a reference to the default ServiceScope, more precisely the Root Scope, or in other words, a scope global to the whole page.
What does it mean ?
It means that the same scope is shared by all your components and all the service instances it contains ! This is not a problem at all if your service instance doesn’t keep any state tied to your component instance. However, if that is the case, that might cause some issues! Why? Because if you have several instances of the same WebPart on the page, they will all share the same service instance ! If you change the state of the service, it will be changed for all your instances. Let’s see an example; I created of small WebPart responsible for displaying the configured list title and its number of items. It is using the ListService described above through the this.context.serviceScope. In the property change handler, I use the service configure() method to pass the WebPart properties and needed context.
That seems to work well, no big deal here 🙂 Let’s see if I add a second WebPart.
We can see that both WebParts are actually using the same configuration instance even if their properties remain different ! That must be addressed because, most likely, several instances of a same WebPart will be added to page with different configurations.
In the case of our services will use a WebPart specific configuration, we will need a WebPart-scoped serviceScope.
The ServiceScope API allows to do that and is smart enough so that we can only specified what is specific to a scope or pertains to the root scope: Child scopes.
A child scope is, as its name tells, a child of the root scope, it means that it might have its specific instances, but all the other instances are fetched from the parent scope (the root scope in our case).
We can create a child scope by using the startNewChild() method of a service scope instance, it has to be configured and its finish() method should have been executed for this new scope to be ready to use. Then we have to deal with the child scope referencing because the this.context.serviceScope will still refer to the root scope.
Inspired by my dependency injection experience in other technologies, I came up with a class implementation that you need to use in the onInit() method of your SPFx component.
Then, in the onInit() method of your component, you can write something like
Let’s see the output with that code in place
Now, we can see each WebPart has its own service scope and work properly.
WebPart-scoped service scope
This is how we can make sure services we develop in our SPFx solutions that the proper instance is used in the right context. Somehow, if you are working on this kind of “WebPart-scoped” (or component-scoped) services intended to be used by others, make sure they are properly documented so the consumer developers will know what he/she should take care of!
Hopefully, you liked this post, give your feedback !
You can find the shared repo of my lab here