Skip to main content

Observers

Observers are reactive data streams that emit values whenever configurator state changes. They let you build UIs that stay in sync with the configuration without polling or manual state tracking.

All observers live under window.mimeeqApp.observers and are grouped by domain — product, pricing, optionSets, history, config, and others. Each observer is an RxJS Observable that you .subscribe() to.

Callback Shape

Every observer callback receives a single object with two optional properties:

interface SubscribeValueWrapper<T> {
newValue?: T | null;
oldValue?: T | null;
}
window.mimeeqApp.observers.product.sku.subscribe(({ newValue, oldValue }) => {
console.log(`SKU changed from "${oldValue}" to "${newValue}"`);
});

Both properties are optional and nullable. oldValue is undefined on the first emission (see below). Always guard against null/undefined before using the value.

Initial Value on Subscribe

Observers emit the current state immediately when you subscribe — you don't have to wait for the next change. If the data is already available (e.g., the product has loaded), your callback fires right away with newValue set and oldValue undefined. If the data isn't available yet, the first emission will have newValue as null or undefined.

This means you can use a single subscription both to catch the current state and to react to future changes:

window.mimeeqApp.observers.pricing.prices.subscribe(({ newValue }) => {
if (newValue) {
// Runs immediately if price is already loaded,
// AND on every subsequent price change
updatePriceDisplay(newValue);
}
});

Subscription Management

.subscribe() returns an RxJS Subscription object. Proper subscription management is important for two reasons:

Avoid duplicate callbacks. Every .subscribe() call registers a new callback. If you subscribe to the same observer multiple times with the same handler (e.g., because your initialization code runs again), your callback will fire multiple times per change. Always check whether you've already subscribed before subscribing again.

Prevent memory leaks. Subscriptions keep their callbacks alive even after your UI is removed from the DOM. In SPAs, custom UI code, or any scenario where the configurator can mount and unmount, failing to unsubscribe means orphaned callbacks continue to run — and accumulate on repeated mount/unmount cycles.

The recommended pattern is to store all subscriptions in an array and unsubscribe in a single cleanup function:

const subscriptions = [];

function init() {
subscriptions.push(
window.mimeeqApp.observers.optionSets.blocks.subscribe(({ newValue }) => {
if (newValue) renderOptions(newValue);
}),
);

subscriptions.push(
window.mimeeqApp.observers.pricing.prices.subscribe(({ newValue }) => {
if (newValue) updatePrice(newValue);
}),
);
}

function cleanup() {
subscriptions.forEach(sub => sub.unsubscribe());
subscriptions.length = 0;
}

Always call cleanup() when the user leaves the configurator, your component unmounts, or before re-initializing. See Custom UI Guide — Cleaning Up for a more complete class-based pattern.

Key Observer Data Shapes

The sections below show the data shapes for the most commonly used observers. For the complete list, see the Observers API Reference.

pricing.prices — PriceData

The primary pricing observer. Emits whenever the price recalculates (option change, quantity change, dealer change).

interface PriceData {
price: number; // Total price for current quantity
unitPrice: number; // Price per single unit
currency: string; // Currency code (e.g., "EUR", "USD")
deliveryTime?: string; // Estimated delivery (if configured)
levels?: PriceLevel[]; // Quantity break tiers
itemMasters?: ItemMasterEntry[]; // Per-component data (if Item Master enabled)
}
window.mimeeqApp.observers.pricing.prices.subscribe(({ newValue }) => {
if (newValue) {
document.getElementById('price').textContent =
`${newValue.unitPrice.toFixed(2)} ${newValue.currency}`;
}
});

product.mainProductData — SimpleProduct

Complete product definition including metadata, option blocks, views, and pricing settings. Emits once after the product loads and again if the product is swapped.

interface SimpleProduct {
metadata: {
name: string;
code: string;
description: string | null;
brand?: string;
status: Status;
mode: string; // "STANDARD" | "MODULAR"
};
productId: string;
blocks?: OptionSetsBlocks;
groups?: BlockGroups;
views?: string[];
pricing?: PricingSettings;
// ... additional fields
}
window.mimeeqApp.observers.product.mainProductData.subscribe(({ newValue }) => {
if (newValue) {
document.getElementById('product-name').textContent = newValue.metadata.name;
}
});

product.sku — string

The SKU code for the current configuration. Changes on every option selection that affects the code structure.

window.mimeeqApp.observers.product.sku.subscribe(({ newValue }) => {
if (typeof newValue === 'string') {
document.getElementById('sku').textContent = newValue;
}
});

optionSets.blocks — PreparedBlock[]

Array of option blocks available for the current product, with all hidden options filtered out. For standard products this is all blocks; for modular products it reflects the currently selected component. This is the primary observer for building custom option panel UIs.

window.mimeeqApp.observers.optionSets.blocks.subscribe(({ newValue }) => {
if (newValue) {
const container = document.getElementById('options');
container.innerHTML = '';

newValue.forEach(block => {
const section = document.createElement('div');
section.textContent = block.name;

block.options?.forEach(option => {
const btn = document.createElement('button');
btn.textContent = option.name;
btn.onclick = () =>
window.mimeeqApp.actions.markOptionByBlockNameAndOptionCode(
block.blockName,
option.code,
);
section.appendChild(btn);
});

container.appendChild(section);
});
}
});

optionSets.selectedOptions — Record<string, SelectedOptions>

A record where keys are instance IDs and values are arrays of selected options. For standard products, there is always a single entry keyed by "SINGLE_PRODUCT_ID". For modular products, each product instance on the scene has its own entry.

window.mimeeqApp.observers.optionSets.selectedOptions.subscribe(({ newValue }) => {
if (newValue) {
// Standard product — single instance
const options = newValue["SINGLE_PRODUCT_ID"];
options?.forEach(option => {
console.log(`${option.blockName}: ${option.code}`);
});

// Modular product — multiple instances
Object.entries(newValue).forEach(([instanceId, options]) => {
console.log(`Instance ${instanceId}: ${options.length} options`);
});
}
});

Observer Groups

The full observer tree is organized into these groups:

GroupAccess pathWhat it covers
Productobservers.product.*Product data, SKU, configuration code, loading state, views, tabs
Pricingobservers.pricing.*Prices, quantity, price type, dealers, loading state
Option Setsobservers.optionSets.*Blocks, selected options, all options
Historyobservers.history.*Undo/redo stack, current position
Configobservers.config.*Embed settings, canvas background, instance ID
Modularobservers.modular.*Scene data, selected products, snapping points (modular products only)
Miscobservers.misc.*Feature flags, AR state, dimensions, other states

For detailed type definitions and per-observer documentation, see the Observers API Reference.

Events vs Observers

Events and observers serve different purposes and most integrations use both:

EventsObservers
ModelFire-and-forget notificationsContinuous state streams
When to useAnalytics, side effects, "something just happened"UI sync, "what is the current state?"
Initial valueNo — you only get future eventsYes — emits current state on subscribe
APIdocument.addEventListener(name, handler)window.mimeeqApp.observers.group.name.subscribe(callback)

See Events for the full event catalog.