Registering new personalization settings
Table of Contents
Technical details
Along with the existing personalization settings, we introduced a generic interface for registering new settings and using them in specific places in the application.
Previous Behavior
For any new personalization setting, a developer would have to add:
- A new personalization key to the Personalization service
- Methods to set and get the value of the setting from the database to the Personalization service
- Specific code that uses this setting where it's needed
New Behaviour
The user can directly register the setting in the place it will be used without touching the Personalization Service.
The new interface can be found in PersonalizationService (Public API).
How to use it
- Inject
mx.SolutionBuilderAgent.PersonalizationServicein the place where you need to set/get a setting and callregister(). The setting appears in the personalization panel immediately. - If you need to read the setting value in another place, use the
get()method.
If you need to update the value, use theset()method.
The interface does not have to know about how the setting itself affects the app. In order to give freedom to the developer and simplify logic, the interface allows to read/write to user storage only.
There are 2 types of supported settings: leaf settings and drilldown settings.
How does the register() method work?
| Parameter | Description | Required/Optional |
|---|---|---|
| key | Unique id; dot notation nests under a drilldown parent | Required |
| title | Setting name | Required |
| valueType | Required for leaf settings: boolean | 'string | number | Required |
| category | Groups settings under a heading; omitted = uncategorized | Optional |
| categoryDescription | Displays as a subtitle under the category heading | Optional |
| description | Displays as a subtitle under the label | Optional |
| inputType | Overrides the control inferred from valueType (e.g. 'radio') | Optional |
| defaultValue | Shows the default value until changed, and on reset | Optional |
| value | This parameter is rarely set directly; The stored value normally wins | Optional |
| position | Lower sorts earlier in the panel. The default value is 999 | Optional |
| hideInPanel | Preserves the value without showing a panel row | Optional |
| options | Required only for select/radio controls; see below |
Optional |
| disabled | Greys out the control but keeps it visible | Optional |
| icon | Sets the material icon shown on drilldown rows | Optional |
| drilldownDescription | Sets the description shown once inside a drilldown; only meaningful on a parent | Optional |
In order to register a drilldown setting:
- register a parent key
- then register child keys using dot notation.
The parent is automatically rendered as a drilldown container; no extra config is needed.
Call `nregister('my-feature') to remove the setting from the panel.
Example
Parent
this.personalizationService.register({ key: 'my-feature', title: 'myFeatureKey', category: 'myCategoryKey', });
Children - dot notation links them to the parentthis.personalizationService.register({ key: 'my-feature.sub-setting', title: 'myFeatureSubKey', valueType: 'string', defaultValue: 'default',});
Control type inference
`valueType` is required and determines the default UI control:
- `'boolean'` → toggle
- `'string'` + `options` → select, `'string'` (no options) → text input
- `'number'` + `options` → select, `'number'` (no options) → number input
Override with inputType parameter
You can force a control to use a specific inputType instead of the inferred one:
inputType: 'radio' // Forces radio buttons instead of inferred toggle
Open panel to specific category
From any component or service, inject PersonalizationPanelService. Panel opens and smoothly scrolls to the specified category group.
personalizationPanelService.openToCategory(`category-i18n-key`);
I18n integration
All string fields accept:
- Plain string: used literally
- i18n key: looked up via i18n.get() at registration time
- Fallback: if key doesn't exist, original string is used
- For dynamic/computed text, resolve first to avoid re-lookup:
personalizationService.register({
key: 'version-display',
title: i18n.get('shell.version'), // Pre-resolved, won't be re-looked-up});
Setting values at runtime
ALWAYS use get() / set() for reading/writing values:const current = await personalizationService.get('my-key');await personalizationService.set('my-key', newValue);
NOT direct mutation:setting.value = newValue; // Wrong — won't persist
Storage and persistence
- Values persist to user storage via UserStorageService
- Reset button resets to defaultValue
- Reset All resets all settings at once
- onChange callbacks trigger on value change
Accessibility
- Escape: Close panel
- Tab: Focus wrapping (last → first element)
- Arrow keys (radio groups): Navigate between options
- Space (radio groups): Select focused option