Hi SharePoint Devs,
Today, I am going to discuss a small Proof of Concept I worked today that might be interesting to write SPFx code: Easy and elegant Dependency Injection !
Services in SharePoint Framework
SPFx has built-in artifacts to create services and consume them. It is a class named ServiceScope. It is a container in which you can register service instances. From your consumer code, you call its consume() method to get an instance of the service you need.
Register a custom service
To register a service of our own, we need to declare 3 things:
- The service contract (a TypeScript interface)
- The service default implementation
- The ServiceKey to identify the service
In SPFx, services must always have a default implementation !
Let’s create a Greetings Service that will return a message
From a consumer code, we can call the consume() method of the ServiceScope instance to be able to consume the appropriate service. Obiously, the consumer code must have a reference to that ServiceScope instance
Default and child Scopes
There is a default instance of the ServiceScope that we might consider as the Root Service Scope. We can also create child scopes, on which we can override the instances of some services. It is particularly useful when we need to have specific implementations according to environment (Unit testing, Workbench, Prod, …) or specific configurations (e.g. WebPart or Extensions properties).
Typically, the configuration of child scope is done at the startup of the application, in the onInit() method of the WebPart or Extension. This method returns a promise, until this promise is resolved, a loading screen will be displayed and then the render() method is called
The ServiceScope has a whenFinished() method that calls a specified callback when the scope configuration is finished. We can use this method in conjunction with the onInit() to make sure the services are properly configured before starting the rendering.
The ServiceScope reference
Something that might happen to be pretty annoying is that we need to pass a reference to the ServiceScope in any consumer code. A easier way would be to keep that reference in a Singleton instance accessible from anywhere. Let’s create a DependenciesManager class and a Single exported instance
And the consumer code
A touch of Elegance
So far, we ease up the problem of the ServiceScope reference to pass in to each consumer code. Inspired by serveral Frameworks and techniques I’ve been using, I would like to simply declare the services I need and use them seamlessly.
In TypeScript, we can use decorators to annotate some code and execute some logic in a declarative way. The idea was, when I annotate a field of a class with @inject() decorator, it calls the inject method of the DependenciesManager.
Some tricky part with the SharePoint Framework is that decorators will be evaluated before the ServiceScope is actually ready and available. I then need to go the other way around. The decorators will add the references they are applied to in an array. In The Dependencies Manager, when the ServiceScope is configured, all the references will be assigned with the appropriate service instance.
An elegant dependency injection might look like
Isn’t it elegant code ?
All the code of this PoC could be found here. Please give your feedback and thoughts about it !