--- title: Coveo Headless (SAP Commerce Cloud) slug: ob480177 canonical_url: https://docs.coveo.com/en/ob480177/ collection: coveo-for-commerce source_format: adoc --- # Coveo Headless (SAP Commerce Cloud) [Coveo Headless](https://docs.coveo.com/en/lcdf0493/) is a library designed for developing UI components powered by Coveo. For Commerce, Headless exposes the Commerce Engine which leverages the [Commerce API](https://docs.coveo.com/en/103/) to query products and the [Event Protocol](https://docs.coveo.com/en/3188/) to send analytics events to Coveo. This article assumes you're already familiar with general Headless concepts and focuses specifically on integrating Headless with your SAP Commerce Cloud Composable Storefront (formerly SAP Spartacus). > **Note** > > If you're looking to implement server-side rendering (SSR) with Headless and SAP Composable Storefront, reach out to your Coveo team for support in adopting them. ## Prerequisites Before integrating Headless with your SAP Commerce Cloud Composable Storefront, review the [Build commerce interfaces](https://docs.coveo.com/en/o4ue6279/) section. This section provides an overview of building commerce interfaces with Headless, configuring your interfaces, and understanding how the Commerce API interacts with the [Coveo Merchandising Hub](https://docs.coveo.com/en/o5290573/). ## Integrate Coveo Headless with SAP Commerce Cloud Composable Storefront When integrating Headless with your SAP Commerce Cloud Composable Storefront, you have the flexibility to choose the integration layer that best suits your needs. While there are [several possible layers](https://help.sap.com/docs/SAP_COMMERCE_COMPOSABLE_STOREFRONT/eaef8c61b6d9477daf75bff9ac1b7eb4/5a1394b745374f59b98c409754fbdaf7.html#loio3f6197ab8b0c43969ecf0184dd974763) where you can integrate Headless with your SAP Composable Storefront, the recommended approach is to integrate at the _component layer_. By overriding specific components, you can manage different aspects of interface's functionality (facets, pagination, sorting) using separate controllers. This mirrors the modular design of the Headless library where each [controller](https://docs.coveo.com/en/o6r70022#headless-controllers) is an abstraction that simplifies the implementation of a specific Coveo-powered UI feature or component. ### Initializing the engine Before you can override components, you must initialize the Headless commerce engine. Create the `src/app/coveo/services/engine.ts` file and add the following code: ```typescript import { Injectable } from '@angular/core'; import { CommerceEngine, buildCommerceEngine, getOrganizationEndpoints, Context, buildContext, buildSearchBox, } from '@coveo/headless/commerce'; @Injectable({ providedIn: 'root', }) export class CoveoEngineService { private headlessEngine: CommerceEngine; private context: Context; public constructor() { this.headlessEngine = this.initializeEngine(); this.context = buildContext(this.headlessEngine); <2> } private initializeEngine(): CommerceEngine { <1> return buildCommerceEngine({ configuration: { organizationId: '', accessToken: '', context: { language: '', currency: '', country: '', view: { url: '', }, }, analytics: { trackingId: '', }, }, }); } public getEngine(): CommerceEngine { return this.headlessEngine; } public updateView(viewUrl: string): void { <3> this.context.setView({ url: viewUrl }); } } ``` <1> Replace the placeholders with your actual values. For more details, see [Initialize the commerce engine](https://docs.coveo.com/en/o6r70022#initialize-the-headless-commerce-engine). <2> Build the `context` controller using the initialized engine. <3> Create a helper method to set the `view` object on the `context` controller. This is crucial for managing the state of the current URL using Headless. For more details, see [Navigating between pages](https://docs.coveo.com/en/o7v87331/). > **Note** > > If your storefront supports user authentication, you should authenticate via search token instead of hard coding an API key in the code. > For more details, see [Authenticate via search token](https://docs.coveo.com/en/ladf0004/). Now, to ensure this service is instantiated and the engine is initialized when the application starts, inject this service within your `app` component. Modify `src/app/app.component.ts` in the following manner: ```typescript import { Component } from '@angular/core'; import { CoveoEngineService } from '../coveo/services/engine'; <1> @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent { title = 'mystore'; constructor(private engineService: CoveoEngineService) {} <2> } ``` <1> Import the newly created `CoveoEngineService` service. <2> Inject the `CoveoEngineService` service into the `AppComponent` constructor. ### Overriding components Once, the engine has been initialized, you can begin to [override and replace CMS components](https://help.sap.com/docs/SAP_COMMERCE_COMPOSABLE_STOREFRONT/eaef8c61b6d9477daf75bff9ac1b7eb4/864a3158bf9f49c99e6196e4e0d27323.html) with Coveo-powered components. This section provides an example of how to override the `SearchBoxComponent` and `SearchResultsListComponent` components with custom Headless-powered Coveo components. Once you have a good understanding of how to override components, you can apply the same approach to other components such as sorting, facets, and pagination. **Override the search box component**
Details [discrete]
Override the search box component Create a `src/app/coveo/components/coveo-search-box` folder. First, in the `src/app/coveo/components/coveo-search-box/coveo-search-box.component.ts` file, add the following code: ```typescript import {Component, OnInit} from '@angular/core'; import {buildSearchBox, SearchBox} from '@coveo/headless/commerce'; import {CoveoEngineService} from "../../services/engine"; import {Router} from "@angular/router"; @Component({ selector: 'coveo-search-box', templateUrl: './search-box.component.html', styleUrls: ['./search-box.component.scss'], }) export class CoveoSearchBoxComponent implements OnInit { public headlessSearchBox!: SearchBox; public searchText: string = ''; public suggestions: { highlightedValue: string; rawValue: string; }[] = []; public constructor(private coveoEngineService: CoveoEngineService, private router: Router) {} public updateState() { this.suggestions = this.headlessSearchBox.state.suggestions; } public onSelect(value: string) { <3> this.headlessSearchBox.selectSuggestion(value); const encodedValue = encodeURIComponent(value); this.router.navigate([`/search/${encodedValue}`]); } public onInput() { <2> this.headlessSearchBox.updateText(this.searchText); } public search() { <4> if (!this.headlessSearchBox.state.isLoading) { this.headlessSearchBox.submit(); const encodedValue = encodeURIComponent(this.headlessSearchBox.state.value); this.router.navigate([`/search/${encodedValue}`]); } } private initializeController() { this.headlessSearchBox = buildSearchBox(this.coveoEngineService.getEngine()); } public ngOnInit() { <1> this.initializeController(); this.headlessSearchBox.subscribe(() => this.updateState()); } } ``` <1> On component initialization, initialize an instance of the `SearchBox` controller and subscribe to it's state. <2> When a user inputs text, update the state of the controller. <3> Create a method that selects a query suggestion value and searches for the results corresponding to the selected value. <4> Search for the query when the user wants to perform a search. Next, add the following code to the `src/app/coveo/components/coveo-search-box/coveo-search-box.component.html` file: ```html
type="text" placeholder="Search" [(ngModel)]="searchText" (input)="onInput()" (keyup.enter)="search()" class="search-input" />
    <2>
  • {{ suggestion.rawValue }}
``` <1> Create an `input` search box which will update the `searchText` and thus the state of the `SearchBox` controller as the user types in a query. Additionally, when the user is ready to enter the query, call the `search` method created previously to submit it via Headless. <2> Showcase suggestions using the state of the `SearchBox` controller and call `onSelect()` when the user clicks a suggestion. Finally, add custom styling to change the appearance of our custom search box. Create a `src/app/coveo/components/coveo-search-box/coveo-search-box.component.scss` file and add the following: ```scss .search-container { position: relative; width: 100%; } .search-input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; } .suggestions-list { position: absolute; top: 100%; left: 0; right: 0; border: 1px solid #ccc; border-radius: 4px; background: white; max-height: 200px; overflow-y: auto; z-index: 1000; list-style: none; padding: 0; margin: 0; } .suggestion-item { padding: 10px; cursor: pointer; color: black; } .suggestion-item:hover { background-color: #f0f0f0; } ``` Now, to replace the original `SearchBoxComponent` with the new `CoveoSearchBoxComponent`, modify the configuration `src/app/spartacus/spartacus-configuration.module.ts` file. ```typescript import { NgModule } from '@angular/core'; import { translationChunksConfig, translations } from "@spartacus/assets"; import { FeaturesConfig, I18nConfig, OccConfig, provideConfig, SiteContextConfig } from "@spartacus/core"; import { defaultCmsContentProviders, layoutConfig, mediaConfig } from "@spartacus/storefront"; import { CoveoSearchBoxComponent } from '../../coveo/components/search-box/search-box.component'; <1> import {FormsModule} from "@angular/forms"; @NgModule({ declarations: [ <2> CoveoSearchBoxComponent ], imports: [ FormsModule ], providers: [provideConfig(layoutConfig), provideConfig(mediaConfig), ...defaultCmsContentProviders, provideConfig({ backend: { occ: { baseUrl: 'https://api.ct2lwawz2z-coveosolu1-d1-public.model-t.cc.commerce.ondemand.com/', } }, }), provideConfig({ context: {}, }), provideConfig({ i18n: { resources: translations, chunks: translationChunksConfig, fallbackLang: 'en' }, }), provideConfig({ features: { level: '2211.27' } }), provideConfig({ cmsComponents: { <3> SearchBoxComponent: { component: CoveoSearchBoxComponent } } })] }) export class SpartacusConfigurationModule { } ``` <1> Import the `CoveoSearchBoxComponent`. <2> Add `CoveoSearchBoxComponent` to the declarations array. <3> Update the `cmsComponents` configuration to use `CoveoSearchBoxComponent` instead of `SearchBoxComponent`. This configuration ensures that the framework uses `CoveoSearchBoxComponent` whenever `SearchBoxComponent` is requested. This approach is a common way to override or extend default components provided by the storefront. #### .Override the result list component
Details [discrete]
Override the search result list component Overriding the `SearchResultsListComponent` will be similar to how what was done for `SearchBoxComponent`. Create a `src/app/coveo/components/coveo-result-list` folder. First, in the `src/app/coveo/components/coveo-search-box/coveo-result-list.component.ts` file, add the following code: ```typescript import {ChangeDetectorRef, Component, OnInit} from '@angular/core'; import {buildSearch, Product, Search} from '@coveo/headless/commerce'; import { CoveoEngineService } from '../../services/engine'; @Component({ selector: 'coveo-result-list', templateUrl: './coveo-result-list.component.html', styleUrls: ['./coveo-result-list.component.scss'], }) export class CoveoResultListComponent implements OnInit { private headlessSearchController!: Search; products!: Product[]; public constructor(private engineService: CoveoEngineService, private cdr: ChangeDetectorRef) {} private initializeController() { this.headlessSearchController = buildSearch(this.engineService.getEngine()); if (this.headlessSearchController) { this.headlessSearchController.subscribe(() => { this.products = this.headlessSearchController.state.products; this.cdr.detectChanges(); }); } } public ngOnInit(): void { this.initializeController(); } } ``` Next, add the following code to the `src/app/coveo/components/coveo-result-list/coveo-result-list.component.html` file: ```html
{{ product.excerpt }}
Name {{ product.ec_shortdesc }}
Price {{ product.ec_price }}
``` Finally, add custom styling to change the appearance of result list. Create a `src/app/coveo/components/coveo-result-list/coveo-result-list.component.scss` file and add the following: ```scss .flex-row { display: flex; flex-direction: row; } .flex-column { display: flex; flex-direction: column; } .mat-caption { color: #7a7a7a; span:first-child { font-weight: 500; } } a { color: #2e45ba; font-size: 20px; text-decoration: none; } ``` Now, to replace the original `SearchResultsListComponent` with the new `CoveoResultListComponent`, modify the configuration `src/app/spartacus/spartacus-configuration.module.ts` file. ```typescript import { CoveoResultListComponent } from '../../coveo/components/coveo-result-list/coveo-result-list.component'; // ... @NgModule({ declarations: [ CoveoSearchBoxComponent, CoveoResultListComponent ], // ... providers: [ // ... provideConfig({ cmsComponents: { SearchBoxComponent: { component: CoveoSearchBoxComponent }, SearchResultsListComponent : { component: CoveoResultListComponent } } })] }) ``` This configuration ensures that the framework uses `CoveoResultListComponent` whenever `SearchResultsListComponent` is requested. #### For more details on building your search, listing, and recommendation interfaces with Headless, see the following: * [Build commerce search pages](https://docs.coveo.com/en/o4ue0200/) * [Build commerce listing pages](https://docs.coveo.com/en/o4ue0471/) * [Build commerce recommendation slots](https://docs.coveo.com/en/o4ue0204/) ## What's next? Once you've integrated Headless with your SAP Commerce Cloud Composable Storefront to power your [product discovery solutions](https://docs.coveo.com/en/o9cf0524/), make sure you're capturing the right analytics data to create reports and power [Coveo Machine Learning](https://docs.coveo.com/en/188/) [models](https://docs.coveo.com/en/1012/). For more information on capturing events, see the [Event Protocol](https://docs.coveo.com/en/o1n91230#capture-events) documentation.