Try our conversational search powered by Generative AI!

Remko Jantzen
Aug 2, 2021
  2654
(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
Developer meetups in Stockholm & Helsinki

It's time for developer meetups! Next month we will be in Stockholm and Helsinki. Join us for getting the latest updates from Optimizely, be inspir...

Magnus Kjellander | Feb 23, 2024

Roll Your Own Security Headers

Proper security headers are a must for your Optimizely driven website. There are a variety of tools out there that will help with this, but when...

Ethan Schofer | Feb 21, 2024

Migrate Catalog content properties

A colleague asked me yesterday – how do we migrate properties of catalog content. There is, unfortunately, no official way to do it. There are...

Quan Mai | Feb 20, 2024 | Syndicated blog

Adjust log levels in Optimizely DXP

You may adjust the log levels for your site in Optimizely DXP yourself, but only for the Integration environment. Follow this step-by-step guide.

Tomas Hensrud Gulla | Feb 20, 2024 | Syndicated blog

Introducing Search & Navigation Dashboard for Resource Usage

We're excited to unveil the latest addition to the Search & Navigation suite: a dashboard designed to proactively monitor your resource usage. It's...

Edvin Dackelid Johansson | Feb 20, 2024

Introducing Jhoose Security Module V2.0

Version V2.0 of the Jhoose Security module has been released and is available via the Optimizely nuget feed. This update not only squashes several...

Andrew Markham | Feb 19, 2024 | Syndicated blog