Code Stories

Spartacus 4.0 – New Features and Best Practice

Written by Ramin Ahmadi | 01 June 2022

Introduction

Spartacus 4 is drastically different from its older versions. If you are looking to migrate to Spartacus 4 from a previous version, you first need to understand those changes before taking the leap. It is also important to know about new features introduced in Version 4 if you are starting Spartacus development from it. 

As a Spartacus developer, I have made applications in older versions of Spartacus and there have been times when I had to update methods or services that I developed to the new application. Quite often, moving code to the new application is not enough and I had to study Spartacus’ documentation to successfully move my extended or customised services into the new application. 

Therefore, I decided to write this blog post to help other developers to learn more about the changes. In this article, I will explain major changes introduced up to Version 4.3 and also discuss newly introduced features so that you can decide if migration to Version 4 is suitable for your project. 

Structural Changes

 The first major change since Version 3 is the folder structure. In Version 4, the Spartacus folder located in the app folder contains AppModule and SpartacusModule, which is where BaseStorefrontModule, SpartacusFeaturesModule and SpartacusConfigurationModule can be found. I highly recommend not introducing any new module or component in this file in order to keep the reference clean. Any customised or new module/component/file can be stationed relative to either SpartacusFeaturesModule, SpartacusConfigurationModule or BaseStorefrontModule and there is no need to introduce a new module in the main SpartacusModule file.

import { NgModule } from '@angular/core';
import { BaseStorefrontModule } from '@spartacus/storefront';
import { SpartacusConfigurationModule } from './spartacus-configuration.module';
import { SpartacusFeaturesModule } from './spartacus-features.module';

@NgModule({
  imports: [
    BaseStorefrontModule,
    SpartacusFeaturesModule,
    SpartacusConfigurationModule,
  ],
  exports: [BaseStorefrontModule],
})
export class SpartacusModule {}

Reference Code for SpartacusModule

BaseStorefrontModule contains essential modules that are necessary for most Spartacus applications. This module is a necessity as it provides basic definitions for the app such as page layout, global messaging etc. One advantage of separating the base module from the rest of the app is to keep the feature module smaller.

I recommend keeping the base module as is and not including the app’s newly introduced module in this one. One module that I changed in my recent Spartacus project is PageLayoutModule to define a fully customised layout based on tailored designs driven by the customer’s requirements and user base community. However, I did not overwrite the layout config in order to protect the out-of-the-box or default page structure defined in the layout configuration file. 

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { BaseCoreModule } from '@spartacus/core';
import { GlobalMessageComponentModule } from './cms-components/misc/global-message/global-message.module';
import { OutletRefModule } from './cms-structure/outlet/outlet-ref/outlet-ref.module';
import { OutletModule } from './cms-structure/outlet/outlet.module';
import { PageComponentModule } from './cms-structure/page/component/page-component.module';
import { PageLayoutModule } from './cms-structure/page/page-layout/page-layout.module';
import { PageSlotModule } from './cms-structure/page/slot/page-slot.module';
import { PwaModule } from './cms-structure/pwa/pwa.module';
import { RoutingModule } from './cms-structure/routing/routing.module';
import { SeoModule } from './cms-structure/seo/seo.module';
import { KeyboardFocusModule } from './layout/a11y/keyboard-focus/keyboard-focus.module';
import { SkipLinkModule } from './layout/a11y/skip-link/skip-link.module';
import { LayoutModule } from './layout/layout.module';
import { StorefrontComponentModule } from './layout/main/storefront-component.module';
import { MediaModule } from './shared/components/media/media.module';

@NgModule({
  imports: [
    BaseCoreModule.forRoot(),
    RouterModule,
    GlobalMessageComponentModule,
    OutletModule,
    OutletRefModule,
    PwaModule,
    PageLayoutModule,
    SeoModule,
    PageComponentModule.forRoot(),
    PageSlotModule,
    SkipLinkModule,
    KeyboardFocusModule,
    LayoutModule,
    RoutingModule.forRoot(),
    MediaModule.forRoot(),
    OutletModule.forRoot(),
    StorefrontComponentModule,
  ],
  exports: [LayoutModule, StorefrontComponentModule],
})
export class BaseStorefrontModule {}

Reference Code for BaseStorefrontModule

SpartacusFeaturesModule is a big module file that contains predefined Spartacus feature modules and is treated as an index for major modules in the app. It is a reference to create and import entry modules in SpartacusFeaturesModule and lazy load child modules in them. If a module is imported here, it will be loaded as soon as the application loads, whether it is immediately necessary or not. For a large Spartacus application, this means loading modules that might not be necessary at the beginning of the application, which consequently impacts the loading time of the app. By introducing entry modules that contain lazy loaded modules, the initial bundle size decreases, which in return decreases load time.

I recommend utilising entry feature modules as much as possible and if there is a need for a new feature follow the same structure in your app.

Newly introduced entry modules can replace existing modules, in which case I recommend using the “Custom” prefix to distinguish the new module from the original Spartacus module. For example, ProductDetailsPageModule will be named CustomProductDetailsPageModule and replace the old one. A good thing about this convention is that the original module name is saved, making it easier for future debugging or refactoring. Adding the custom prefix also makes it easier to distinguish custom from out of the box features.

import { NgModule } from '@angular/core';
import {
  AnonymousConsentsModule,AuthModule,CartModule,CartOccModule,CostCenterOccModule,ExternalRoutesModule,
  OrderOccModule,ProductModule,ProductOccModule,UserOccTransitional_4_2_Module,UserTransitional_4_2_Module,
} from '@spartacus/core';
import {
  AddressBookModule,AnonymousConsentManagementBannerModule,AnonymousConsentsDialogModule,BannerCarouselModule,BannerModule,BreadcrumbModule,CartComponentModule,CartPageEventModule,CategoryNavigationModule,CmsParagraphModule,ConsentManagementModule,FooterNavigationModule,HamburgerMenuModule,HomePageEventModule,JsonLdBuilderModule,LinkModule,LoginRouteModule,LogoutModule,MyCouponsModule,MyInterestsModule,NavigationEventModule,NavigationModule,NotificationPreferenceModule,PageTitleModule,PaymentMethodsModule,ProductCarouselModule,ProductDetailsPageModule,ProductFacetNavigationModule,ProductImagesModule,ProductIntroModule,ProductListingPageModule,ProductListModule,ProductPageEventModule,ProductReferencesModule,ProductSummaryModule,ProductTabsModule,SearchBoxModule,SiteContextSelectorModule,StockNotificationModule,TabParagraphContainerModule,WishListModule,
} from '@spartacus/storefront';
import { environment } from '../../environments/environment';
import { AdministrationFeatureModule } from './features/administration-feature.module';
import { AsmFeatureModule } from './features/asm-feature.module';
import { BulkPricingFeatureModule } from './features/bulk-pricing-feature.module';
import { CdcFeatureModule } from './features/cdc-feature.module';
import { CdsFeatureModule } from './features/cds-feature.module';
import { ImportExportFeatureModule } from './features/import-export-feature.module';
import { CheckoutFeatureModule } from './features/checkout-feature.module';
import { OrderApprovalFeatureModule } from './features/order-approval-feature.module';
import { OrderFeatureModule } from './features/order-feature.module';
import { DigitalPaymentsFeatureModule } from './features/digital-payments-feature.module';
import { ProductConfiguratorRulebasedCpqFeatureModule } from './features/product-configurator-rulebased-cpq-feature.module';
import { ProductConfiguratorRulebasedFeatureModule } from './features/product-configurator-rulebased-feature.module';
import { ProductConfiguratorTextfieldFeatureModule } from './features/product-configurator-textfield-feature.module';
import { QualtricsFeatureModule } from './features/qualtrics-feature.module';
import { QuickOrderFeatureModule } from './features/quick-order-feature.module';
import { SavedCartFeatureModule } from './features/saved-cart-feature.module';
import { SmartEditFeatureModule } from './features/smartedit-feature.module';
import { StorefinderFeatureModule } from './features/storefinder-feature.module';
import { TrackingFeatureModule } from './features/tracking-feature.module';
import { UserFeatureModule } from './features/user-feature.module';
import { VariantsFeatureModule } from './features/variants-feature.module';
import { ImageZoomFeatureModule } from './features/image-zoom-feature.module';

const featureModules = [];

if (environment.b2b) {
  featureModules.push(
    AdministrationFeatureModule,
    BulkPricingFeatureModule,
    OrderApprovalFeatureModule
  );
}
if (environment.cdc) {
  featureModules.push(CdcFeatureModule);
}
if (environment.cds) {
  featureModules.push(CdsFeatureModule);
}
if (environment.cpq) {
  featureModules.push(ProductConfiguratorRulebasedCpqFeatureModule);
} else {
  featureModules.push(ProductConfiguratorRulebasedFeatureModule);
}
if (environment.digitalPayments) {
  featureModules.push(DigitalPaymentsFeatureModule);
}

@NgModule({
  imports: [
    // Auth Core
    AuthModule.forRoot(),
    LogoutModule, // will be come part of auth package
    LoginRouteModule, // will be come part of auth package

    // Basic Cms Components
    HamburgerMenuModule,
    SiteContextSelectorModule,
    LinkModule,
    BannerModule,
    CmsParagraphModule,
    TabParagraphContainerModule,
    BannerCarouselModule,
    CategoryNavigationModule,
    NavigationModule,
    FooterNavigationModule,
    PageTitleModule,
    BreadcrumbModule,

    // User Core
    UserTransitional_4_2_Module,
    UserOccTransitional_4_2_Module,
    // User UI
    AddressBookModule,
    PaymentMethodsModule,
    NotificationPreferenceModule,
    MyInterestsModule,
    StockNotificationModule,
    ConsentManagementModule,
    MyCouponsModule,

    // Anonymous Consents Core
    AnonymousConsentsModule.forRoot(),
    // Anonymous Consents UI
    AnonymousConsentsDialogModule,
    AnonymousConsentManagementBannerModule,

    // Product Core
    ProductModule.forRoot(),
    ProductOccModule,

    // Product UI
    ProductDetailsPageModule,
    ProductListingPageModule,
    ProductListModule,
    SearchBoxModule,
    ProductFacetNavigationModule,
    ProductTabsModule,
    ProductCarouselModule,
    ProductReferencesModule,
    ProductImagesModule,
    ProductSummaryModule,
    ProductIntroModule,

    // Cart Core
    CartModule.forRoot(),
    CartOccModule,
    // Cart UI
    CartComponentModule,
    WishListModule,

    // Cost Center
    CostCenterOccModule,

    // Order
    OrderOccModule,

    // Page Events
    NavigationEventModule,
    HomePageEventModule,
    CartPageEventModule,
    ProductPageEventModule,

    /************************* Opt-in features *************************/

    ExternalRoutesModule.forRoot(), // to opt-in explicitly, is added by default schematics
    JsonLdBuilderModule,

    /************************* Feature libraries *************************/
    UserFeatureModule,
    CheckoutFeatureModule,
    AsmFeatureModule,
    StorefinderFeatureModule,
    QualtricsFeatureModule,
    SmartEditFeatureModule,
    TrackingFeatureModule,
    VariantsFeatureModule,
    SavedCartFeatureModule,
    OrderFeatureModule,
    QuickOrderFeatureModule,
    ImportExportFeatureModule,
    ProductConfiguratorTextfieldFeatureModule,
    ImageZoomFeatureModule,
    ...featureModules,
  ],
})
export class SpartacusFeaturesModule {}

Reference Code for SpartacusFeaturesModule

SpartacusConfigurationModule includes general Spartacus configuration. Project-specific configurations such as layout config, site context and PWA config are stored in this file. In my recent project based on Spartacus 4, I needed to introduce a new set of page templates and layouts due to heavy UI customisation. I introduced a custom layout configuration file that encapsulates all available page templates and page renders in frontend refers to the file to load Slots and components accordingly. 

Inside this file, we can define baseURL and site context including urlParameters, language, baseSite value and currency (read more about it here). A recommended approach for baseUrl is to set the configuration as default OCC so the commerce cloud can detect the environment and pick the right value as baseUrl changes in each environment. Another key configuration is the path to translation files. 

I recommend breaking the translation file into smaller chunks (translation files) for each module so that the related files can be found easily.

NgModule({
 declarations: [],
 imports: [],
 providers: [
   provideConfig(CustomlayoutConfig),
   provideConfig(CustommediaConfig),
   ...defaultCmsContentProviders,
   provideConfig(<OccConfig>{
     backend: {
     occ: {
         // baseUrl: 'https://localhost:9002',
       },
     },
   }),
   provideConfig(<SiteContextConfig>{
     context: {
       urlParameters: ['baseSite', 'language', 'currency'],
       language: ['en'],
       baseSite: ['app-spa'],
       currency: ['USD'],
     },
   }),
   provideConfig(<I18nConfig>{
     i18n: {
       resources: translations,
       chunks: CustomtranslationChunksConfig,
       fallbackLang: 'en',
     },
   }),
   provideConfig(<FeaturesConfig>{
     features: {
       level: '4.3',
     },
   }),
   provideConfig(defaultB2bOccConfig),
   provideConfig(defaultB2bCheckoutConfig),
 ],
})
export class SpartacusConfigurationModule {}

Reference Code for SpartacusConfigurationModule

Feature Changes

Many new features have been introduced in Version 4 so far making it easier to develop a Spartacus website. Multiple features that I personally had to introduce in my previous Spartacus V3 project now are available out of the box which means organisations can potentially save development time and resources.

From this point on, I will be discussing the new features introduced in Spartacus Version 4.

 

Cloning Option when Restoring Saved Cart

I needed this feature in my last project! It would have been very helpful if it was available before but the good news is if you need to make a copy of a saved shopping cart and reuse it later, you can do it easily by using Version 4.0 and above. 

This feature particularly comes in handy when multiple items are added and the user plans to continue making the purchase on a regular basis. By using the save cart feature that was introduced in Spartacus Version 3.2, users can save a current cart on the shopping cart page, as illustrated in Image 1 below. When a user activates a cart, it disappears and loads in the shopping cart, saving time and effort to load the same items again and again (Image 2). 

But there is a catch. In Version 3.2, if you need to refer to a cart that you had saved and activated later, it won’t exist in the saved shopping cart section anymore because it has been activated by the user. To address this issue, a new checkbox is introduced in Version 4.0 that allows the user to keep a copy of the cart — an exact clone of the order — as shown in Image 3.
 

Image 2: ‘Save Cart’ View in Spartacus 4

 

Image 3: ‘Copy Saved Cart’ Feature Newly Introduced in Version 4

 

SAP Digital Payments Integration

Arguably one of the best features introduced in Version 4.1 is the integration of SAP Digital Payments. In previous versions, Spartacus only provided on-account or card payments, which was not ideal.

Nowadays, most businesses tend to use payment platforms that cover multiple avenues, including both online and point of sales payments so that sales data is available in one big pool. This data helps with getting good customer insight, optimising revenue, enhancing security and supporting local payments in different countries. 

Systems like Stripe or Adyen offer these services and a lack of integration with such platforms disconnects online sales from the rest of the payment solution. Hence, Spartacus introduced SAP Digital Payments integration to provide a better experience to both business owners and customers. 

By using SAP Digital Payments, a user’s request to add a new card is sent to SAP Digital Payments to select the appropriate service provider. In return, SAP Digital Payments provides a page with a unique sessionID and the user can then enter their card details in the provided screen.

To enable this feature you can use the below command in the root directory of your Spartacus application. 

ng add @spartacus/digital-payments

Reference Code for SpartacusModule

 

Quick Order

One of the high-demand B2B functionalities of an e-commerce website is the ability to add multiple items to the shopping cart as fast as possible. 

In a previous project, for which I worked with Spartacus 3.2, I used a CSV upload method to gather all items from a file quickly and add them to the shopping cart. But with the newly introduced quick order feature, users can add items and quantities quickly.

Screenshot of ‘Quick Order’ Feature

 

Cart Validation

One of the issues with Spartacus checkout in the past versions was stock availability. 

A user could simply add a product to the cart and leave it at that, then come back to it in a couple of days and try to proceed to place an order. By that time, however, the product could go out of stock. This required the system to check stock availability every time a user tried to place an order. 

In my previous project, I introduced an order simulation method that checks the availability of a product before the checkout process. In its 4.2 Version, Spartacus uses a configuration called cart validation that forces stock checks before and during checkout automatically. Spartacus checks the availability of stocks at every step to ensure that the requested quantity of products in the current shopping cart is available as the user proceeds towards checkout.

If a product goes unavailable during the process, Spartacus will transfer the user to the shopping cart with a message notifying them of lack of availability of a particular product(s) so the user can decide if they need to reduce the quantity of the product(s) or remove it entirely. 

Conclusion

Considering the updates in Version 4.0 and above, as professional Spartacus developers, it is on us to consider trade-offs and recommend proceeding with migration to 4.0. Even if your project is starting in near future and you are only familiar with Version 3 and below, I would highly recommend using the latest version and studying the new features thoroughly. 

As always, Spartacus’ roadmap is a great reference to decide if a feature needs to be created from scratch and added to Spartacus or if you should wait for it to be released with the next bunch of new features. Happy reading!