Try our conversational search powered by Generative AI!

Remko Jantzen
Aug 2, 2021
  2731
(0 votes)

Frontend: Adding application services - Into Foundation Spa React series, Part 6

In this sixth - and final - installment of the  "Into Foundation-Spa-React" series, the focus is on the service container bundled within the Foundation Spa React and how it might be leveraged when building your own solution based upon the core libraries that power Foundation Spa React.

First is the use-case. I most cases you wouldn't need to add your own services, as you can leverage the context/provider and hooks from React to make a service available. The case where this will be most useful is where you need to replace one of the standard services provided by the framework, need to interact with the bootstrapping process, and/or have supporting services that you need to be able to reference when needed.

The most used services from the service container are the Content Repository and Content Delivery Client. The first provides a proxy to the second, adding (when running in-browser and non-edit) caching of content items using IndexDB. The second is used directly for "non-content" operations, such as authentication, controller methods.

In order to interact with the bootstrapping process, you'll need to provide an instance of Core.IInitializableModule, which can easily be created by extending Core.BaseInitializableModule. During the bootstrapping process, the system invokes the three methods in the following order:

  • After the core services have been registered, but before any initialization has taken place: ConfigureContainer; This is where you should add/configure services within the container.
  • After the container has been created, when the initialization creates the Redux state container: GetStateReducer; This is where you can add your reducers for the global state container
  • As the last step of the bootstrapping process: StartModule; here you can execute any logic needed to bootstrap your own logic.

At this time all three methods run synchronously, which could cause a long execution time on the main thread if not used sparsely and critically.

As you might have noticed, the Core.IInitializableModule has a SortOrder, which defines the execution sequence of the modules. They are executed in ascending order, the system guarantees that all modules with a lower SortOrder have been executed successfully, however, modules with the same SortOrder can be executed in parallel, before or after the current module. Modules with a higher SortOrder will always be executed after this module.

In action

So, a very long explanation, let's see how this works in practice, by looking at one of the core modules within the SPA: "Core State Engine"

export class StateModule extends BaseInitializableModule implements IInitializableModule
{
    protected name = "Core State Engine";
 
    public SortOrder = 40;
 
    public StartModule(context: IEpiserverContext): void
    {
        const store = context.getStore();
        const state = store.getState() as PartialAppState;
        const cfg = context.serviceContainer.getService<Readonly<IConfig>>(DefaultServices.Config);
 
        // Setup CD-API Language to respond to the state changes, ensuring
        // that it always takes the current CD-API instance from the container.
        Tools.observeStore<string, CmsAppState>(
            store,
            (x) => x?.OptiContentCloud?.currentLanguage || cfg.defaultLanguage,
            (newValue) => {
                if (newValue) {
                    const cdAPI = context.serviceContainer.getService<IContentDeliveryAPI>(DefaultServices.ContentDeliveryAPI_V2);
                    cdAPI.Language = newValue;
                }
            }
        );
 
        // Make sure the current language is applied
        const language = state?.OptiContentCloud?.currentLanguage;
        if (!language) {
            store.dispatch({
                type: "OptiContentCloud/SetState",
                currentLanguage: cfg.defaultLanguage
            })
        } else {
            const cdAPI = context.serviceContainer.getService<IContentDeliveryAPI>(DefaultServices.ContentDeliveryAPI_V2);
            cdAPI.Language = language || cfg.defaultLanguage;
        }
 
    }
 
    /**
     * Return the standard state reducer for the CMS Status
     */
    public GetStateReducer : () => IStateReducerInfo<any> = () => CmsStateReducer;
}

So what happens in this module? First, we see that there's no override of the ConfigureContainer method; this is possible as we're extending BaseInitializableModule that provides basic empty methods for each of the three required methods from IInitializableModule. So this module does not affect the Container itself. Then in the second step, it registers the reducer for Redux that will manage the state, then finally, when the site starts it ensures that we have a language in the state and that it is synchronized with the current language setting of the ContentDeliveryAPI.

Ok, you've created your IInitializableModule, great, but JavaScript doesn't allow for automatic class discovery. So the last step is to register it within your configuration.

// Partial configuration shown, with just the items relevant to this article.
export const config : SpaConfig = {
    // Initialization modules
    modules: [
        new CommerceInitialization()
    ]
}

This being JavaScript, the instantiation is only required because the module extends BaseInitializableModule, it is possible to create a static object that implements the interface as well, however that would mean that you need to implement the full interface.

But wait, the title said it was possible to add application services? Yes, it is possible, as shown in the Routing Module. 

export default class RoutingModule extends BaseInitializableModule implements IInitializableModule
{
    protected name = "Optimizely CMS Routing";
    public readonly SortOrder = 20;
   
    /**
     * Ensure the configuration object within the service container contains a "*" route. If
     * this "*" route is not claimed by the implementation, it will be added as fall-back to
     * Episerver CMS based routing.
     *
     * @param {IServiceContainer} container The Service Container to update
     */
    public ConfigureContainer(container: IServiceContainer) : void
    {
        const config = container.getService<AppConfig>(DefaultServices.Config);
        let haveStar = false;
        config.routes = config.routes || [];
        config.routes.forEach(c => haveStar = haveStar || c.path === "*");
        if (!haveStar) config.routes.push({
            path: "*",
            component: RoutedComponent
        });
    }
}

So instead of getting a service from the container (in this case Configuration), it is also possible to set/add services into the container. The DefaultServices here is just a list of predefined strings, so for your own services, use whatever name you like. A good convention would be [Module].[Service], for example, "Routing.Router";

 And that's a wrap. Feel free to star the project on GitHub, leave feedback here or on GitHub, use it to kick-start your own Optimizely powered SPA. Anyway, I hope this series gave a little insight into the inner workings of Foundation-Spa-React.

Aug 02, 2021

Comments

Please login to comment.
Latest blogs
Optimizely and the never-ending story of the missing globe!

I've worked with Optimizely CMS for 14 years, and there are two things I'm obsessed with: Link validation and the globe that keeps disappearing on...

Tomas Hensrud Gulla | Apr 18, 2024 | Syndicated blog

Visitor Groups Usage Report For Optimizely CMS 12

This add-on offers detailed information on how visitor groups are used and how effective they are within Optimizely CMS. Editors can monitor and...

Adnan Zameer | Apr 18, 2024 | Syndicated blog

Azure AI Language – Abstractive Summarisation in Optimizely CMS

In this article, I show how the abstraction summarisation feature provided by the Azure AI Language platform, can be used within Optimizely CMS to...

Anil Patel | Apr 18, 2024 | Syndicated blog

Fix your Search & Navigation (Find) indexing job, please

Once upon a time, a colleague asked me to look into a customer database with weird spikes in database log usage. (You might start to wonder why I a...

Quan Mai | Apr 17, 2024 | Syndicated blog

The A/A Test: What You Need to Know

Sure, we all know what an A/B test can do. But what is an A/A test? How is it different? With an A/B test, we know that we can take a webpage (our...

Lindsey Rogers | Apr 15, 2024

.Net Core Timezone ID's Windows vs Linux

Hey all, First post here and I would like to talk about Timezone ID's and How Windows and Linux systems use different IDs. We currently run a .NET...

sheider | Apr 15, 2024