Remko Jantzen
Aug 2, 2021
  2942
(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
Copy Optimizely SaaS CMS Settings to ENV Format Via Bookmarklet

Do you work with multiple Optimizely SaaS CMS instances? Use a bookmarklet to automatically copy them to your clipboard, ready to paste into your e...

Daniel Isaacs | Dec 22, 2024 | Syndicated blog

Increase timeout for long running SQL queries using SQL addon

Learn how to increase the timeout for long running SQL queries using the SQL addon.

Tomas Hensrud Gulla | Dec 20, 2024 | Syndicated blog

Overriding the help text for the Name property in Optimizely CMS

I recently received a question about how to override the Help text for the built-in Name property in Optimizely CMS, so I decided to document my...

Tomas Hensrud Gulla | Dec 20, 2024 | Syndicated blog

Resize Images on the Fly with Optimizely DXP's New CDN Feature

With the latest release, you can now resize images on demand using the Content Delivery Network (CDN). This means no more storing multiple versions...

Satata Satez | Dec 19, 2024

Simplify Optimizely CMS Configuration with JSON Schema

Optimizely CMS is a powerful and versatile platform for content management, offering extensive configuration options that allow developers to...

Hieu Nguyen | Dec 19, 2024

Useful Optimizely CMS Web Components

A list of useful Optimizely CMS components that can be used in add-ons.

Bartosz Sekula | Dec 18, 2024 | Syndicated blog