Coveo Headless
Coveo Headless
This is for:
DeveloperCoveo Headless is a library designed for developing UI components powered by Coveo. For Commerce, Headless exposes the Commerce Engine which leverages the Commerce API to query products and the Event Protocol 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 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.
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 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 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 need to initialize the Headless commerce engine.
Create the src/app/coveo/services/engine.ts
file and add the following code:
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);
}
private initializeEngine(): CommerceEngine {
return buildCommerceEngine({
configuration: {
organizationId: '<ORG_ID>',
accessToken: '<TOKEN>',
context: {
language: '<LANGUAGE>',
currency: '<CURRENCY>',
country: '<COUNTRY>',
view: {
url: '<URL>',
},
},
analytics: {
trackingId: '<TRACKING_ID>',
},
},
});
}
public getEngine(): CommerceEngine {
return this.headlessEngine;
}
public updateView(viewUrl: string): void {
this.context.setView({ url: viewUrl });
}
}
Replace the placeholders with your actual values. For more details, see Initialize the commerce engine. | |
Build the context controller using the initialized engine. |
|
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. |
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. |
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:
import { Component } from '@angular/core';
import { CoveoEngineService } from '../coveo/services/engine';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'mystore';
constructor(private engineService: CoveoEngineService) {}
}
Import the newly created CoveoEngineService service. |
|
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 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
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:
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) {
this.headlessSearchBox.selectSuggestion(value);
const encodedValue = encodeURIComponent(value);
this.router.navigate([`/search/${encodedValue}`]);
}
public onInput() {
this.headlessSearchBox.updateText(this.searchText);
}
public search() {
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() {
this.initializeController();
this.headlessSearchBox.subscribe(() => this.updateState());
}
}
On component initialization, initialize an instance of the SearchBox controller and subscribe to it’s state. |
|
When a user inputs text, update the state of the controller. | |
Create a method that selects a query suggestion value and searches for the resuts corresponding to the selected value. | |
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:
<div class="search-container">
<input
type="text"
placeholder="Search"
[(ngModel)]="searchText"
(input)="onInput()"
(keyup.enter)="search()"
class="search-input"
/>
<ul *ngIf="suggestions.length > 0" class="suggestions-list">
<li
*ngFor="let suggestion of suggestions"
(click)="onSelect(suggestion.rawValue)"
class="suggestion-item"
>
</li>
</ul>
</div>
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. |
|
Showcase suggestions using the state of the SearchBox controller and call onSelect() when the user clicks on 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:
.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.
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';
import {FormsModule} from "@angular/forms";
@NgModule({
declarations: [
CoveoSearchBoxComponent
],
imports: [
FormsModule
],
providers: [provideConfig(layoutConfig), provideConfig(mediaConfig), ...defaultCmsContentProviders, provideConfig(<OccConfig>{
backend: {
occ: {
baseUrl: 'https://api.ct2lwawz2z-coveosolu1-d1-public.model-t.cc.commerce.ondemand.com/',
}
},
}), provideConfig(<SiteContextConfig>{
context: {},
}), provideConfig(<I18nConfig>{
i18n: {
resources: translations,
chunks: translationChunksConfig,
fallbackLang: 'en'
},
}), provideConfig(<FeaturesConfig>{
features: {
level: '2211.27'
}
}), provideConfig({
cmsComponents: {
SearchBoxComponent: {
component: CoveoSearchBoxComponent
}
}
})]
})
export class SpartacusConfigurationModule { }
Import the CoveoSearchBoxComponent . |
|
Add CoveoSearchBoxComponent to the declarations array. |
|
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
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:
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:
<div *ngFor="let product of products">
<div class="mb-5">
<div class="mb-2">
<a
href="http://localhost:4200/electronics-spa/en/USD/product//"
></a
>
</div>
<div class="mb-1">
<div class="mat-body-1"></div>
</div>
<div class="flex-row">
<div class="mr-4">
<div *ngIf="product.ec_shortdesc" class="mat-caption">
<span class="mr-1">Name</span>
<span></span>
</div>
</div>
<div class="mr-4">
<div *ngIf="product.ec_price" class="mat-caption">
<span class="mr-1">Price</span>
<span></span>
</div>
</div>
</div>
</div>
</div>
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:
.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.
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:
What’s next?
Once you’ve integrated Headless with your SAP Commerce Cloud Composable Storefront to power your product discovery solutions, make sure you’re capturing the right analytics data to create reports and power Coveo Machine Learning models.
For more information on capturing events, see the Event Protocol documentation.