QuantityFormatter Lifecycle & Integration
This page covers the QuantityFormatter lifecycle APIs — readiness signals, spec registration, multi-system access, and auto-refreshing handles. These complement the core Parsing and Formatting workflows and Provider setup.
Who should read this
| Role | Question you're asking | Jump to |
|---|---|---|
| Application Developer | "How do I know when formatting is ready after app init?" | Readiness & Initialization |
| Tool Provider | "How do I register my domain's formatting specs and keep them fresh across reloads?" | Spec Provider Integration |
| Tool Developer | "How do I get auto-refreshing specs for my measure tool?" | FormatSpecHandle |
| Tool Consumer | "How do I display a formatted value in my UI component and keep it current?" | FormatSpecHandle, Multi-System KoQ Access |
Readiness & Initialization
The QuantityFormatter loads formatting and parsing specs asynchronously. Specs are not available immediately after IModelApp.startup() — you need to synchronize with the readiness lifecycle.
whenInitialized
QuantityFormatter.whenInitialized is a one-shot promise that resolves after the first successful initialization. It resolves once and stays resolved — safe to await at any point in app startup.
Example Code
isReady
QuantityFormatter.isReady is a synchronous boolean. Returns false until the first reload completes, then true. Use it as a guard before synchronous spec lookups.
Example Code
onFormattingReady
QuantityFormatter.onFormattingReady fires after every reload completes — initialization, unit system changes, and provider changes. This is the primary signal for keeping UI and caches in sync.
Example Code
Set-backed event: QuantityFormatter.onFormattingReady uses BeUnorderedUiEvent — a Set-backed event where listeners can safely add or remove themselves during emission and unsubscribe in O(1) via the closure returned by
addListener().
Spec Provider Integration
This section is for teams that supply domain-specific formatting specs to the QuantityFormatter registry — for example, Civil's DisplayUnitFormatter or any package that provides KindOfQuantity definitions beyond the built-in defaults.
The problem
When the formatter reloads (unit system change, provider change, app init), the internal spec registry is rebuilt from IModelApp.formatsProvider. Any specs your domain registered via QuantityFormatter.addFormattingSpecsToRegistry are lost and need to be re-registered.
The pattern
- Subscribe to QuantityFormatter.onBeforeFormattingReady to register async work before the formatter is considered ready
- In your listener, call
collector.addPendingWork(promise)with a promise that re-registers your domain's KoQ specs via QuantityFormatter.addFormattingSpecsToRegistry - The formatter awaits all pending work (with a 10-second timeout) before emitting QuantityFormatter.onFormattingReady
- Downstream tool consumers using FormatSpecHandle or getSpecsByNameAndUnit will see your domain specs immediately when
onFormattingReadyfires
Event ordering note: The formatter follows a two-phase ready flow:
onBeforeFormattingReady— Fires first. Providers register async work via the FormattingReadyCollector passed to listeners. Callcollector.addPendingWork(promise)to register each async task.- The formatter awaits all pending work (with a 10-second timeout). Rejections are logged as warnings but do not block readiness.
onFormattingReady— Fires after all provider work has settled. Consumers can now safely read specs knowing all providers have finished registering.Pattern: Providers use
onBeforeFormattingReady, consumers useonFormattingReady.
Example: Registering async provider work before formatting is ready
Example: Domain spec provider that re-registers on reload
Composite-keyed registry
The spec registry is keyed by KindOfQuantity name, persistence unit, and unit system ([koqName][persistenceUnit][unitSystem]). This means the same KoQ with different persistence units or different unit systems can coexist.
Use QuantityFormatter.getSpecsByNameAndUnit to retrieve a specific entry by its composite key. Pass an optional system parameter to retrieve specs for a non-active unit system:
Example Code
Multi-System KoQ Access
The spec registry supports storing and retrieving specs for multiple unit systems simultaneously. This is useful when you need to display the same measurement in different unit systems — for example, showing both metric and imperial values side-by-side.
- QuantityFormatter.getSpecsByNameAndUnit accepts an optional
systemparameter to retrieve specs for a specific unit system - FormatSpecHandle accepts an optional
systemparameter to pin the handle to a specific unit system - QuantityFormatter.addFormattingSpecsToRegistry accepts an optional
systemparameter to register specs for a specific unit system
Example: Format a KoQ in multiple unit systems
FormatSpecHandle
FormatSpecHandle is a cacheable, auto-refreshing handle to formatting specs. It's the recommended way for tool developers and UI components to hold a reference to a formatting spec without manually subscribing to reload events.
Key behaviors:
- Fallback formatting —
format(value)returnsvalue.toString()if specs aren't loaded yet, so your tool always produces output - Auto-refresh — The handle subscribes to QuantityFormatter.onFormattingReady and updates its internal specs on every reload
- Disposable — Call
dispose()or use ausingdeclaration to unsubscribe from events and avoid leaks
Basic Usage
Create a handle via QuantityFormatter.getFormatSpecHandle, use it to format values, and dispose when done:
Example Code
Using Declaration
FormatSpecHandle implements Symbol.dispose, so you can use a using declaration for automatic cleanup when the handle goes out of scope:
Example Code
When to use FormatSpecHandle vs events
| Pattern | Best for | Why |
|---|---|---|
FormatSpecHandle |
Tool developers, UI components that format a specific KoQ | Zero boilerplate — just create, format, dispose. Auto-refreshes. |
onBeforeFormattingReady |
Spec providers that register domain specs, async loading | Async work is awaited before the formatter is considered ready. Specs are available to all onFormattingReady consumers. |
onFormattingReady |
Consumers that refresh UI or read specs after each reload | Fires after all provider work has settled — safe to read any registered specs. |
isReady / whenInitialized |
App startup gates, lazy initialization | One-time checks before first use. |
Migrating from Multiple Event Subscriptions
If your code subscribes to multiple QuantityFormatter events to stay in sync with formatting changes, you can simplify by migrating to QuantityFormatter.onFormattingReady or FormatSpecHandle.
Before: Multiple event subscriptions
A common legacy pattern involves subscribing to several events to cover all the ways formatting can change:
Each of these events covers a different reload trigger, but they all mean the same thing: "formatting specs have changed."
After: Single event or auto-refreshing handle
Option A — For spec providers (packages that register domain KoQs):
Replace all subscriptions with QuantityFormatter.onBeforeFormattingReady. Register your async loading work via the FormattingReadyCollector — the formatter awaits all pending work before emitting QuantityFormatter.onFormattingReady.
Option B — For tool developers and UI components (recommended):
Replace event subscriptions entirely with FormatSpecHandle. Each handle auto-refreshes when formatting changes and provides a format() method with a built-in fallback:
Migration summary
| Old pattern | New pattern | When to use |
|---|---|---|
| 2-4 event subscriptions + async spec registration | onBeforeFormattingReady |
You re-register domain specs or perform async loading before ready |
| Event subscription + manual spec re-fetch | onFormattingReady |
You refresh UI or read specs after each reload |
Event subscription + findFormatterSpecByQuantityType() |
FormatSpecHandle |
You format values for display in a tool or UI component |
| Guard pattern against double-subscription | Neither needed | Events fire exactly once per reload; FormatSpecHandle manages its own subscription |
See Also
- Providers - Setting up UnitsProvider and FormatsProvider
- Parsing and Formatting - FormatterSpec, ParserSpec, and tool integration patterns
- Migrating from QuantityType to KindOfQuantity
Last Updated: 15 April, 2026