Angular Integration
Angular integration guide with basic (full-page and modal) and advanced widget methods for Sophi.
This page is organized in two levels:
- Basic setup: copy-paste examples for full-page and modal usage
- Advanced setup: optional runtime controls (
sendToggleHistory,sendProductReply,triggerProductReply)
For GTM script loading and script-load failures, use SDK GTM troubleshooting.
Required in every integration
No matter which UI pattern you use (full page or modal), implement all three:
- Handle
add_to_cart - Handle
send_to_checkout - Call
widget.updateUserCart(cart)when cart changes
Basic setup
1) Access object from window
const SophiWidgetClass = (window as any).SophiWebSDK?.SophiWidget;
if (!SophiWidgetClass) {
throw new Error("Sophi SDK not loaded yet");
}2) Initialize widget
const widget = new SophiWidgetClass();
await widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-container",
environment: "production",
});3) Full-page minimal example
import { AfterViewInit, Component, Input, OnChanges, OnDestroy, SimpleChanges } from "@angular/core";
import { Router } from "@angular/router";
type CartItem = { productId: string; variantId?: string; quantity: number };
@Component({
selector: "app-sophi-full-page",
template: `<div id="sophi-widget-container" style="width: 100%; height: 100vh;"></div>`,
})
export class SophiFullPageComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input() cartId = "";
@Input() cartItems: CartItem[] = [];
private widget: any = null;
constructor(private readonly router: Router) {}
async ngAfterViewInit(): Promise<void> {
const SophiWidgetClass = (window as any).SophiWebSDK?.SophiWidget;
if (!SophiWidgetClass) return;
this.widget = new SophiWidgetClass();
await this.widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-container",
environment: "production",
width: "100%",
height: "100%",
});
this.widget.on("add_to_cart", async (data: { products: Array<{ productId: string; variantId?: string }> }) => {
for (const product of data.products) {
await addToCartInYourApp(product.productId, product.variantId);
}
});
this.widget.on("send_to_checkout", () => {
this.router.navigateByUrl("/checkout");
});
this.syncCart();
}
ngOnChanges(changes: SimpleChanges): void {
if (!this.widget) return;
if (changes["cartId"] || changes["cartItems"]) {
this.syncCart();
}
}
private syncCart(): void {
this.widget.updateUserCart({
cartId: this.cartId,
items: this.cartItems,
});
}
async ngOnDestroy(): Promise<void> {
await this.widget?.destroy?.();
this.widget = null;
}
}4) Modal minimal example
import { Component, Input, OnChanges, OnDestroy, SimpleChanges } from "@angular/core";
import { Router } from "@angular/router";
@Component({
selector: "app-sophi-modal",
template: `
<button type="button" (click)="open()">Open assistant</button>
<div *ngIf="isOpen" role="dialog" aria-modal="true">
<button type="button" (click)="close()">Close</button>
<div id="sophi-widget-modal-container" style="width: 100%; height: 70vh;"></div>
</div>
`,
})
export class SophiModalComponent implements OnChanges, OnDestroy {
@Input() cartId = "";
@Input() cartItems: Array<{ productId: string; variantId?: string; quantity: number }> = [];
isOpen = false;
private widget: any = null;
constructor(private readonly router: Router) {}
async open(): Promise<void> {
this.isOpen = true;
if (this.widget) return;
const SophiWidgetClass = (window as any).SophiWebSDK?.SophiWidget;
if (!SophiWidgetClass) return;
this.widget = new SophiWidgetClass();
await this.widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-modal-container",
environment: "production",
width: "100%",
height: "100%",
});
this.widget.on("add_to_cart", async (data: { products: Array<{ productId: string; variantId?: string }> }) => {
for (const product of data.products) {
await addToCartInYourApp(product.productId, product.variantId);
}
});
this.widget.on("send_to_checkout", () => {
this.close();
this.router.navigateByUrl("/checkout");
});
this.syncCart();
}
close(): void {
this.isOpen = false;
}
ngOnChanges(changes: SimpleChanges): void {
if (!this.widget) return;
if (changes["cartId"] || changes["cartItems"]) {
this.syncCart();
}
}
private syncCart(): void {
this.widget.updateUserCart({
cartId: this.cartId,
items: this.cartItems,
});
}
async ngOnDestroy(): Promise<void> {
await this.widget?.destroy?.();
this.widget = null;
}
}5) Required events and cart sync wiring
widget.on("add_to_cart", async (data) => {
for (const product of data.products) {
await addToCartInYourApp(product.productId, product.variantId);
}
});
widget.on("send_to_checkout", () => {
router.navigateByUrl("/checkout");
});
widget.updateUserCart({
cartId: cartId,
items: cartItems,
});Advanced setup
Call advanced methods only after ready event or widget.isReady():
widget.on("ready", () => {
widget.sendToggleHistory();
widget.triggerProductReply(product);
widget.sendProductReply("Can you tell me more about this item?", product);
});sendToggleHistory: open/close history paneltriggerProductReply: prefill product reply contextsendProductReply: send product-context query
Consent-aware (pre-consent memory-only mode)
To make the assistant available before the visitor accepts cookies,
initialize with storageConsent: false and upgrade in place when consent is
granted. See Cookie Consent Management for the full
concept.
Initialize the widget once with the initial consent value, then call
setStorageConsent(true) whenever your consent service emits a grant — do not
destroy and re-create the widget, or the open chat is lost.
import { AfterViewInit, Component, OnDestroy } from "@angular/core";
import { Subscription } from "rxjs";
import { ConsentService } from "./consent.service"; // your own service
@Component({
selector: "app-sophi-consent-aware",
template: `<div id="sophi-widget-container" style="width: 100%; height: 100vh;"></div>`,
})
export class SophiConsentAwareComponent implements AfterViewInit, OnDestroy {
private widget: any = null;
private sub?: Subscription;
constructor(private readonly consent: ConsentService) {}
async ngAfterViewInit(): Promise<void> {
const SophiWidgetClass = (window as any).SophiWebSDK?.SophiWidget;
if (!SophiWidgetClass) return;
this.widget = new SophiWidgetClass();
await this.widget.init({
apiKey: "YOUR_SOPHI_API_KEY",
container: "#sophi-widget-container",
environment: "production",
width: "100%",
height: "100%",
storageConsent: this.consent.hasConsent(), // false → memory-only
});
// Upgrade the live widget in place when consent is granted.
this.sub = this.consent.changes$.subscribe((granted: boolean) => {
if (granted) {
void this.widget?.setStorageConsent?.(true);
}
});
}
async ngOnDestroy(): Promise<void> {
this.sub?.unsubscribe();
await this.widget?.destroy?.();
this.widget = null;
}
}Security
- Keep
YOUR_SOPHI_API_KEYas placeholder in docs - Do not put real secrets or internal endpoints into client code examples