I’ve recently come off a fairly involved SharePoint Framework (SPFx) project. We went down he React path with it. This was my first React project after using Angular as my main framework for about two years. (This was nothing against Angular — if SPFx had good support for Angular I would probably have used it).
One of the things I really missed with React was the idea of re-usable services that would be injected by the NG run-time via its dependency injection (DI) system (see here for a good discussion in DI overall). I still miss that :).
I never addressed the DI side of things but I did badly want my re-usable services.
SPFx (at least when I started back in November/December 2017) is very component oriented — you can do web parts or extensions. The components can be re-used easily enough but they almost always have a UI aspect to them. I had a lot of service type functions that have no UI and didn’t fit well into the web part / extension tooling SPFx provides.
I solved it by creating services as singletons. The following example portrays a “workflow service.” This service knows how to iterate over available SharePoint worfklows, start them, etc. Here’s singleton part of it:
This follows the Singleton pattern. Here’s where you can look at that in more detail: https://stackoverflow.com/questions/30174078/how-to-define-singleton-in-typescript
I have to import SPHttpContext because I almost always use it in my services. And since these are individual “plain” TS files, they don’t benefit from the auto-wiring you get from an SPFx web part.
Here’s how I initialize a service from a web part:
In the above gist, you can see that I’m calling initializeInstance() on two services, “DocumentsService” and “WorfklowService.”
For me, I had to do this in the web part container as opposed to the React component itself:
(Again, because I need to pass down the http context and this is where it’s easily available; I meant to implement a more elegant approach for this, but it was so easy to do it like this that it just sort of “stuck.”)
And finally, a React component (or even another service) that needs to invoke methods on the service works like this:
At line 17, I invoke getInstance() on the WorkflowService. This doesn’t throw an error since it was already initialized by the container.
At line 19, I invoke the “startWFByDisplayName” method on the service.
This approach has worked out very well. It’s easy to reason about, easy to create new services and easy to know when something goes wrong (usually because I’d try to getInstance() on a service that hadn’t been initialized).
I put all of my re-usable services in a “Services” folder:
It’s important to keep the “singleton” nature of these services in mind. The main implication is that they share one set of data per page load and consequently, really need to be stateless. This was never a problem for me, although in the early goings-on, I did forget this and ran into some difficult-to-debug issues before I fully groked the implications. It actually benefited performance — a service that retrieves a user’s profile can do it just once and return the same profile to any web part that needs it (which in my case, we almost every web part on every page). I may write a separate short blog post on this topic later.
Hope this is useful! Microsoft will likely (if they have not already), provide some framework support for service type features. In the meantime, this does the trick.