Showing posts with label ServiceScope. Show all posts
Showing posts with label ServiceScope. Show all posts

Wednesday, 5 February 2020

SPFx: Using React hooks to globally share service scope between components

In my previous posts, I have written quite a few times about SharePoint Framework service scopes (I will add links at the end of the article). In short, Service Scopes are the SPFx implementation of the Service Locator pattern i.e. a single shared "dictionary" where services (either oob SPFx or custom) are registered and can be consumed from any component in the application.

For example, without using service scopes, if we wanted to make a call to the Microsoft Graph (using MSGraphClient) from within a deeply nested react component, either we would have to pass in the SPFx context down all the components in the tree, or maybe a create a custom service which returns the web part context, and then call that service from within our nested component. Or maybe use redux to globally maintain the context in a single state object.

But with all these approaches (there may be more), testing the components would be difficult as they would have a dependency on the SPFx context which is hard to mock. Waldek Mastykarz has a great post on this.

Also, from a maintenance point of view, it could get tricky as almost all our components would start to depend on the entire context and we could easily loose track of which specific service from the context is needed by the component.

Now with my previous posts on service scopes, even though we were removing the dependency on the SPFx context, one issue still remained that the SPFx service scope was still needed to be passed into the component. We were just replacing the SPFx context with the SPFx service scopes. While this was good from a testing point of view, it wasn't great for maintainability.

Fortunately, in the recent versions of SPFx, React 16.8+ was supported which means that we can take advantage of React hooks. Specifically, the useContext hook. This gives us a very straightforward way to store the SPFx service scope in the global react context (which is different to the SPFx context) and then consume it from any component in our application no matter how deeply nested it is.

Let's see how to achieve this. In these code samples, I am using SPFx v1.10 which is the latest version at the time of writing.

1) The Application Context object


First, we need to create the React application context object which will be used to store and consume the service scope. For now I am only storing the serviceScope in the context. Other values can be stored here as well.


2) React Higher Order Component (HOC)


React hooks can only be used from functional components and not classes. With the SPFx generator creating classes by default and hooks being fairly new, I am sure there is a lot of code out there already which use classes and not functional components. Changing all code to use functional components instead of classes is a non-starter.

Fortunately, there is a way to use react hooks with classes by creating a Higher Order Component (HOC) which is a functional component. We can wrap all our class components with this HOC and safely consume the useContext hook from within this component.

(Update: If you are interested in going down the "full hooks" approach and doing away entirely with classes, Garry Trinder has got you covered. He has created a fork which only uses functional components and hooks so we don't need the HOC. If you want to take this approach, check out the code here: https://github.com/garrytrinder/spfx-servicescopes-hooks)


3) SPFx web part 


Next, we update our SPFx webpart to only pass in the serviceScope once to our top level component:


4) Top level React component 


Our top level component will need to be wrapped with the AppContext so that any nested component will be able to consume it. This just needs to be done once on the top level react component. You will notice that the HelloUser child component does not need any props passed in.


5) Child component  


Due to the Higher Order component and the useContext hook, we are able to access the serviceScope property from withing the child component. We can grab the MSGraphClient from the serviceScope and start making calls to the Graph:

And that's it! This way, we can use the React useContext hook to globally share our SPFx service scope.

Hopefully you have found this post helpful! All code is added in the GitHub repo here: https://github.com/vman/spfx-servicescopes-hooks


Also, if you are interested, here are all my previous articles on SPFx service scopes:

SharePoint Framework: Org Chart web part using Office UI Fabric, React, OData batching and Service scopes

Getting the current context (SPHttpClient, PageContext) in a SharePoint Framework Service

Service Locator pattern in SPFx: Using Service Scopes

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

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: https://github.com/vman/MultiInstanceServiceScopes

Wednesday, 27 March 2019

Service Locator pattern in SPFx: Using Service Scopes

I have written about this topic in the past but with the recent increase in SharePoint Framework adoption and with more features becoming available (e.g. MSGraphClient, AadHttpClient), I felt it would be a good time to revisit this.

With SPFx solutions getting more complex day by day and with lots of components to manage, passing the web part (or extension) context around to different parts of your code can get difficult to maintain real fast.

The problem:


For example, imagine we have created a custom service which needs the MSGraphClient to make a call to the Microsoft Graph and we are consuming this service in our SPFx webpart. To initialise the service, we need to either pass in the entire web part context to it, or explicitly pass in the MSGraphClient object from the context.

In the first case, we are unnecessarily passing in all other objects in the context to this service as well.

And in the second case, our code becomes tightly coupled i.e. in the future, if our service needs something else from the webpart context, we have to update the service as well as the consuming code (i.e. the webpart) to pass in the new dependency.

The solution:


Using the Service Locator pattern in SPFx through service scopes, we can abstract away the implementation details of our custom services from the calling code (i.e. webparts, extensions).

With service scopes, we can get hold of instances of SPFx classes like MSGraphClient, AadHttpClient, SPHttpClient and PageContext from our services without having to explicitly pass them in from the webparts.



In this post, lets see how to achieve this:

1) Calling the MSGraphClient from a custom service:



Consuming the custom service from an SPFx webpart:


If you observe the code, our calling webpart does not have any knowledge of the implementation details of the custom service, specifically the fact that it used the MSGraphClient internally to retrieve the current user details. In the future, if we wanted to change the implementation of the service, we could do it without changing any web part code.

2) Calling the AadHttpClient from a custom service:


Similarly, we can also use service scopes to get hold of an instance of the AadHttpClient class. In the code below, to keep things simple, I am using the AadHttpClient to make a call to the Microsoft Graph. Eventually, the result is the same as the previous code but it should demo how to use the AadHttpClient through service scopes.


Consuming the custom service from an SPFx webpart:



3) Calling the SPHttpClient from a custom service:


And finally, if we just want to make a call to the SharePoint REST API from our SPFx code, we can also use service scopes to get hold of an instance of the SPHttpClient class. The following code also demonstrates how to get an instance of the PageContext class to get different run time context values like the current web url. Using the current web url, we can make a call to the SharePoint REST API to get the web details:


Consuming the custom service from an SPFx webpart:


And that's it for this post! I am planning more posts on this topic in the future so keep an eye out for those :)

As always, the code from this post is available on GitHub: https://github.com/vman/ServiceScopeTestBench

Friday, 12 May 2017

Getting the current context (SPHttpClient, PageContext) in a SharePoint Framework Service

I have briefly written about building SPFx services (using ServiceScopes) in my previous post, SharePoint Framework: Org Chart web part using Office UI Fabric, React and OData batching

In this post, lets have a more in-depth look at how to actually get the current SharePoint context in your service, without passing it in explicitly.

All the code used in this post is available on GitHub: https://github.com/vman/SPFx-Service-Context-Demo


Building an SPFx service using ServiceScopes:


Suppose you want to create a service which you want to call from multiple SPFx webparts. The idea is that the code of your service should load on the page only once even if multiple web parts using the same service are added to the page. There is already some great information out there on building such kind of service in SPFx so I won't repeat it here. Have a look:

Share data through a SharePoint Framework service

Building shared code in SharePoint Framework - revisited

I myself have been working on building an SPFx service as an npm package and then using it in a webpart:
https://github.com/vman/SPFx-Service

But before you go ahead and start using ServiceScopes to build your SPFx service, have a read through this Tech Note from the SPFx team:
https://github.com/SharePoint/sp-dev-docs/wiki/Tech-Note:-ServiceScope-API

To summarise, it is recommended to consider other alternatives first before using ServiceScopes. Other alternatives include explicitly passing in dependencies like SPHttpClient in the constructor of your service, or if your service has multiple dependencies, then build a class which has all the dependencies as properties and then pass an object of the class to your service's constructor.

While these recommendations would work in some scenarios, they might not be viable in all conditions. If you are building your service as an npm package, passing in the current context every time you want to use the service might be very burdensome. Ideally, your service should be able to determine all the information about the current context on its own. So lets have a look in this post on how do to that.

Quick note before moving ahead: Building library packages in SPFx is currently not available but it is on the roadmap. It would be nice to see the "build a library" option in the SPFx Yeoman generator. The UserVoice link for this request is here: https://sharepoint.uservoice.com/forums/329220-sharepoint-dev-platform/suggestions/19224202-add-support-for-library-packages-in-the-sharepoint

In this post, to keep things simple, I am going to build my service in the same package as my SPFx webpart. The idea is to demonstrate the concept of using ServiceScopes to get the current context. It would work exactly the same if the service was part of a different SPFx package. If you want to have a look at how this works, have a look at my github repo here: https://github.com/vman/SPFx-Service


Getting the current context in an SPFx service:


Now that you have decided to build an SPFx service using ServiceScopes, lets have a look at how to get the current context in your service without passing it in explicitly.

First, you need to build a service which accepts a ServiceScope object in the constructor. This is the only dependency your service will need. Also, you will not need to explicitly pass in this dependency. SharePoint Framework will handle this for you.

Here is my code for the service:

Now lets have a look at what is going on in the code:

First, we are importing the SPHttpClient class from the @microsoft/sp-http package and the PageContext class from the @microsoft/sp-page-context package.

These classes expose their own ServiceKeys, so in the constructor of our service, we are using those keys to grab an instance of these classes. These instances of classes will already be initialised thanks to the ServiceScope.whenFinished and ServiceScope.consume methods. We can then use the instances in the methods of our class.

Using the current web url to initialise PnP-JS-Core :


Just as an example, you could use the initialised PageContext object to get the current web url and pass it to the PnP JS library. This can be useful if you want to get lists in the current web:



Consuming the service:


Now you have built your service, it is time to consume it from your SPFx web part. It is very straightforward, you just have to instantiate an object of your class using the ServiceScope.consume method and your are good to go. I am using async/await just to keep things simple, but using Promise.then will also work here:


Have a look at the complete code in this post here: https://github.com/vman/SPFx-Service-Context-Demo

Thanks for reading! Hope you have found this post helpful.