Skip to content

PluginSettingsComponentBase<PluginSettings>

Base class for plugin settings components.

Manages settings data, persistence, validation, and events. Plugin authors extend this class and pass their settings class as PluginSettingsComponentBaseConstructorParams.pluginSettingsClass, whose default-constructed instance supplies the defaults.

Import:

import { PluginSettingsComponentBase } from '@mnaoumov/obsidian-lib/components/plugin-settings-component';

Signature:

export class PluginSettingsComponentBase<PluginSettings extends object> extends ComponentEx implements AsyncEventSource<PluginSettingsComponentBaseEventMap<PluginSettings>>

Extends: ComponentEx

Implements: AsyncEventSource<PluginSettingsComponentBaseEventMap<PluginSettings>>

Constructor

new PluginSettingsComponentBase(params: PluginSettingsComponentBaseConstructorParams<PluginSettings>)

Creates a new plugin settings component.

Properties

PropertyTypeDescription
dataHandlerDataHandlerThe data handler used to load and save the plugin settings.
defaultSettingsReadonlyDeep<PluginSettings>Gets the readonly default settings.
pluginEventSourcePluginEventSourceThe plugin event source the component listens to for external settings changes.
pluginSettingsClassConstructor<PluginSettings, []>The plugin settings class used to construct settings instances.

Methods

MethodReturnsDescription
[Symbol.dispose]()voidDisposes of the component.
(Inherited from ComponentEx)
addChild(component)TComponentAdds a child component.

Mirrors the native Component.addChild contract: if this component is already loaded, the child is loaded immediately, so child._loaded is set before this method returns even when this component has async load logic. The child's async tail (if any) is sequenced into the load promise so a later loadWithPromises call awaits it.

Adding a child BEFORE the first load is legitimate: the child is queued and loaded when this component loads. Adding one AFTER this component has been unloaded is not — the child would never be loaded and never unloaded (a leak), so it is refused with a SilentError. The typical source of such a call is an async method that was suspended on an await when the component got unloaded and then resumed on the dead component; the SilentError unwinds it quietly (see isUnloaded).
(Inherited from ComponentEx)
editAndSave(settingsEditor, context?)Promise<void>Edits the plugin settings and saves them.
ensureLoaded()voidEnsures the component is loaded, throwing if it is not.

Use this to guard public methods that register teardown-bearing resources (via Component.register, Component.registerEvent, Component.registerDomEvent, etc.). Registering before load is unsafe: Component.unload is a no-op while the component is not loaded, so any teardown registered beforehand would never run if the component is unloaded without first being loaded.

The two not-loaded cases are deliberately distinguished. A component that was NEVER loaded is a genuine programming error, so it throws a loud Error. A component that has ALREADY been unloaded is not — it is the expected outcome of work that outlived its component (e.g. an async method suspended on an await while the component was unloaded, then resumed) — so it throws a SilentError, which handleSilentError suppresses, letting such work unwind quietly instead of reporting an async error.
(Inherited from ComponentEx)
ensureSafe(settings)Promise<void>Ensures the settings are safe.

It runs validation for each property and sets the default value if the validation fails.
getInFlightAncestryLoadPromise()null | Promise<void>Returns a Promise that settles once neither this component nor any ComponentEx ancestor has a load in flight, or null when none of them has one right now.

getInFlightLoadPromise covers this component's OWN load only. A sibling this component depends on — most often the owning plugin's settings component, which reads data.json asynchronously — is outside it, but it is inside the load of a shared ancestor. Waiting for every ancestor therefore waits for the whole tree the component was loaded as part of: under PluginBase the chain ends at the plugin's universal wrapper, whose load covers the dependency gate, the feature surface and onloadImpl.

The chain is followed through ComponentEx parents only. A plain Component anywhere between this component and an ancestor ends it, because a plain component records no parent and exposes no load promise.

Never await this from inside a load step of one of those ancestors: the ancestor's load would then be waiting for itself. It is meant for work scheduled OUTSIDE the load, such as a layout-ready handler.
(Inherited from ComponentEx)
getInFlightLoadPromise()null | Promise<void>Returns the component's in-flight load Promise (its onloadAsync plus children), or null when nothing is loading — either the component loaded fully synchronously or its async load has already settled.

Lets a caller scheduled while the component is still loading await the async load tail before acting, instead of racing it: Component.onload has run (so _loaded is set) but onloadAsync may not have finished.

It covers this component's OWN load only. A caller that also depends on a sibling's state — a layout-ready handler reading settings is the common one — wants getInFlightAncestryLoadPromise, which LayoutReadyComponent uses.
(Inherited from ComponentEx)
getSafeCopy(settings)Promise<PluginSettings>Gets a safe copy of the settings.
getTransformer()TransformerGets the transformer.
hasChild(component)booleanChecks whether a component is a direct child of this one.

Only DIRECT children are reported — this is an ownership question, not a containment one. The caller that needs it is one holding several parents and deciding which of them a component belongs to, and for that a grandchild is the wrong answer: removing it from the parent that merely contains its parent would fail.
(Inherited from ComponentEx)
hasLoadErrors()booleanReturns whether the most recent load recorded any failure.

Reflects the outcome of the last load / loadWithPromises: true when onloadAsync or any child's load threw (synchronously or asynchronously). Unlike loadWithPromises, reading this does NOT re-raise the collected errors — it lets a bystander (e.g. a layout-ready handler awaiting the in-flight load) tell whether the load succeeded without adopting a failure that the load's owner is already responsible for.
(Inherited from ComponentEx)
isUnloaded()booleanReturns whether the component has already been unloaded, as opposed to simply not having been loaded yet.

Both states leave _loaded false, but they mean opposite things: a component that was never loaded is still ahead of its lifecycle (queueing children before load is legitimate), while an unloaded one is behind it and must not be used any further. The distinction is tracked by a flag set in load, NOT by an Component.onunload override, because subclasses override onunload and may not call super.

Use it in a long-running async method to abandon work whose component was unloaded mid-flight, when unwinding via the SilentError thrown by ensureLoaded / addChild is not desired.
(Inherited from ComponentEx)
load()voidLoads the component.
(Inherited from ComponentEx)
loadFromFile(isInitialLoad)Promise<void>Loads the plugin settings from the file.
loadWithPromises()null | Promise<void>Loads the component with promises.

Unlike load, this method never rejects with an individual error: every failure raised by onloadAsync or a child's load is collected, and once everything settles the returned Promise rejects with a single AggregateError holding all of them. Non-Error throwables are normalized via ErrorWrapper.create.

The AggregateError is built by createAggregateError, so its own message names the failure rather than being empty: a lone failure lends its message to every aggregate above it, which is what lets a caller assert on the sentence that was actually thrown without walking errors to find it.
(Inherited from ComponentEx)
off(name, callback)voidRemove an event listener.
offref(eventRef)voidRemove an event listener by reference.
on(name, callback, thisArgument?)AsyncEventRefAdd an event listener.
once(name, callback, thisArgument?)AsyncEventRefTrigger an event, executing all the listeners in order even if some of them throw an error.

Add an event listener that will be called only once.
onExternalSettingsChange()Promise<void>Called when the external settings change.
onloadAsync()Promise<void>Loads settings from file and registers event handlers.
onLoadRecord(record)Promise<void>Called when the plugin settings record is loaded from disk.
onLoadSettings(_loadedState, _isInitialLoad)Promise<void>Called when settings are loaded or reloaded.
onSaveSettings(params)Promise<void>Called when settings are saved.
onSavingRecord(_record)Promise<void>Called when the plugin settings record is about to be saved to disk.
registerDisposable(disposable)TDisposableRegisters a Disposable so it is disposed when this component unloads, and returns it unchanged.

The recurring "tie a disposable to the component's lifecycle, then keep using it" idiom: the disposable is disposed on Component.unload (or earlier, if the caller disposes it directly — dispose is expected to be idempotent). Guard with ensureLoaded at the call site when registering before load would be unsafe.
(Inherited from ComponentEx)
registerLegacySettingsConverter(legacySettingsClass, converter)voidRegisters a legacy settings converter.
registerLegacySettingsConverters()voidRegisters the legacy settings converters. Override to register legacy settings converters.
registerValidator(propertyName, validator)voidRegisters a validator for a property.
registerValidators()voidRegisters the validators. Override to register validators for properties.
removeChild(component)TComponentRemoves a child component.
(Inherited from ComponentEx)
revalidate()Promise<Record<PropertyNames<PluginSettings>, string>>Revalidates the settings.
saveToFile(context?)Promise<void>Saves the new plugin settings.
setProperty(propertyName, value)Promise<string>Sets the value of a property.
validate(settings)Promise<ValidationResult<PluginSettings>>Validates the settings.
whenLoadedFromFile()Promise<void>Waits until the settings have been read from data.json at least once.

Anything that reads a setting from outside this component has to wait for this, because until the first loadFromFile completes settings still holds the DEFAULTS — reading it earlier answers with a default rather than with what the user actually stored. That window is easy to miss, since it is empty on a cold start (the workspace layout is not ready yet, so nothing has run) but wide open on a runtime enable from the Community Plugins tab or a re-enable after an update, where a layout-ready handler fires immediately while this component's own async load is still in flight.

Resolves immediately once the settings have loaded, so a caller that arrives late is not stranded waiting for an event that has already been raised. Later reloads (an external data.json change) do not reset it — the question it answers is whether the settings have EVER been read, not whether the most recent read has finished.

Links to this page: