Skip to content
npm

Events reference ​

All events live on NgxViewBuilderApiService. They are small emitters of the library's own, not RxJS:

ts
const dispose = api.onValueChanged.add((e) => { /* ... */ });   // returns a function that detaches
const sub = api.onValueChanged.subscribe((e) => { /* ... */ });  // { unsubscribe(), closed }
api.onValueChanged.once((e) => { /* ... */ });                    // first time only
api.onValueChanged.remove(handler);

Detach in ngOnDestroy. A runtime view, a scope and the builder's Preview each fire on their own instance, and every event is forwarded to the root service you inject, so the same subscription works on any page. When several views are on screen, elementName and elementDataPath in the payload tell them apart.

For one element only, the element handle has its own onValueChanged, onValueChanging and onPropertyChanged.

Values & data ​

EventFires whenPayload
onValueChanging / onValueChangeda value is about to change / changed, by a person, an expression, an action or setValue(){ dataPath, newValue, oldValue, sender, trigger }
onElementValueChanging / onElementValueChangedthe same, with the element resolved{ elementDataPath, newValue, oldValue, trigger, element, host, api }
onElementPropertyChanging / onElementPropertyChangeda property (label, hidden, disabled...) is written{ propertyKey, newValue, oldValue, elementName, elementDataPath, elementType, element, elementDom }

Each change fires once, also when you subscribe on the root service your app injects while the view runs inside <ngx-view-builder-runtime>. Before 0.12.4 those subscribers got every value and table event twice.

trigger is 'input', 'change' or 'blur' for a person, 'programmatic' for code and expressions, 'load' while the view opens. api is the element handle.

ts
api.onElementValueChanged.add(({ elementDataPath, newValue, trigger }) => {
  if (elementDataPath === 'country' && trigger !== 'programmatic') {
    void api.reloadDataSource('city');
  }
});

Validation & completion ​

EventFires whenPayload
onValidating / onValidatedvalidation runs: Validate, Submit, a validate action, validateData(){ isValid, issues }
onCompleteSubmit (the button or a submit action){ isValid, data, issues }
onSaveRequestedthe builder's Save, Ctrl+S, or notifySaveRequested(){ source: 'header' | 'api', timestamp }

issues is a list of { elementName, dataPath, errors: string[] }.

ts
api.onComplete.add(async ({ isValid, data }) => {
  if (!isValid) return;
  await firstValueFrom(http.post('/api/applications', data));
  api.markPristine();
});

Structure & navigation ​

EventFires whenPayload
onStructureChangedthe view definition changed (builder edit, API write, setStructure){ structure }
onCurrentPageChangedanother page is shown{ pageIndex, pageName }
onDialogCloseda view rendered as a dialog closed{ pageIndex, pageName, dialogTitle, reason: 'close-button' | 'api', timestamp }
onTabChanged / onTabChangeRequestedthe builder's tab changed / was asked to change{ tab }
onLanguageChangedthe language switched{ language }

Rendering ​

EventFires whenPayload
onBeforeRender / onRender / onAfterRendera view starts rendering / rendered its models / is in the DOM{ tab, root, phase }
onElementRender / onElementAfterRenderonce per element in each of those phases{ tab, root, phase, elementName, elementDataPath, elementType, elementTestId, element, elementDom, index, total }

They fire on the first render, on a new [pageJson] and on setStructure(). Use them for DOM level integrations: tooltips, analytics attributes, measuring.

ts
api.onElementAfterRender.add(({ elementName, elementDom }) => {
  elementDom?.setAttribute('data-analytics-id', `form.${elementName}`);
});

Element interactions ​

EventFires whenPayload, besides elementName, elementDataPath, element, timestamp
onDynamicTableRowAdded / onDynamicTableRowRemoveda row of a dynamicTable was added / removedrowIndex, row, rows, total
onDynamicPanelItemAdded / onDynamicPanelItemRemovedan entry of a dynamicPanel was added / removedrowIndex, row, rows, total
onFileUploadFilesAdded / onFileUploadFileRemovedfiles were attached / one removedfiles, addedFiles / files, removedFile, removedIndex
onElementTabChangeda tabs element switched tabpreviousTabValue, tabValue, tabIndex
onAccordionItemToggledan accordion section opened or closeditemIndex, itemValue, expanded

The buttons, the rows handle and setValue() on a repeater all fire them.

ts
api.onDynamicTableRowAdded.add(({ elementName, total }) => {
  if (elementName === 'familyMembers' && total >= 5) {
    api.showToast({ title: 'Limit', message: 'At most 5 family members', variant: 'warning' });
  }
});

Data sources ​

Every payload has sourceName, sourceType, contextElementName, contextElementDataPath, fromCache and runtimeContext.

EventFires whenPayload adds
onDataSourceLoadinga request startsstartedAt
onDataSourceLoadedit succeededstartedAt, finishedAt, durationMs, result
onDataSourceLoadFailedit failedstartedAt, finishedAt, durationMs, error, errorMessage
onDataSourceReloadedan element or the API asked for a reloadtimestamp, result

One handler covers spinners and error toasts for every view:

ts
let pending = 0;
api.onDataSourceLoading.add(() => this.loading.set(++pending > 0));
api.onDataSourceLoaded.add(() => this.loading.set(--pending > 0));
api.onDataSourceLoadFailed.add(({ sourceName, errorMessage }) => {
  this.loading.set(--pending > 0);
  api.showToast({ title: `Could not load ${sourceName}`, message: errorMessage, variant: 'error' });
});

Appearance & configuration ​

EventFires whenPayload
onThemeModeChangedlight or dark was set{ theme }
onCustomThemeChangeda custom theme was set{ theme }
onCssVariablesChangedtokens were overridden{ cssVariables }
onCustomCssChangedcustom CSS changed{ cssText }
onCustomCssUrlsChangedstylesheet urls changed{ urls }
onDataSourceTypeSettingsChangedoptional source types were switched{ settings }
onElementDefaultsChangedelement defaults were registered or cleared{ defaults, changedTypes }
onAiAssistantConfigChangedthe AI assistant config was set{ config }

Templates & sidebar library ​

These are the persistence hooks of the template library and the element library: store what they hand you, load it back on startup.

EventFires whenPayload
onTemplateSaveda template was saved in the Templates tab or with saveTemplate(){ template, previousCode, timestamp }
onTemplateDeleteda template was deleted{ code, timestamp }
onTemplatesLoadedloadTemplates() ran{ templates, replace, timestamp }
onSidebarGroupSavedan element was saved to the library (right click, Copy to library) or with saveSidebarGroup(){ group, previousCode, timestamp }
onSidebarTemplateSavedthe same save from the builder, with the item as item{ item, timestamp }
onSidebarGroupDeleteda library item was deleted{ code, timestamp }
onSidebarGroupsLoadedloadSidebarGroups() ran{ groups, replace, timestamp }

The components expose the same as outputs: (templateSaved), (templateDeleted), (templatesLoaded), (sidebarGroupSaved), (sidebarGroupDeleted), (sidebarGroupsLoaded).

Tables ​

EventFires whenPayload
onTableSettingsChanged / onTableSettingsAppliedcolumns were changed by a person or applied from the host{ tableName, elementName, settings, source }
onTableSettingsRequesteda table asks for stored settings{ tableName, elementName, currentSettings }
onTableSettingsSaveRequesteda table asks the host to store settings{ tableName, elementName, settings }
onTableFiltersChanged / onTableFiltersAppliedfilters changed / were applied{ tableName, elementName, filters, source, savedFilter }
onTableSavedFiltersChangedthe saved filter list changed{ tableName, elementName, filters, source }
onTableSavedFiltersRequesteda table asks for stored filter sets{ tableName, elementName, currentFilters }
onTableSavedFilterSaveRequesteda person saved a filter set{ tableName, elementName, filter, currentFilters }

Automation ​

Triggers and rules defined in a view's settings.triggers and settings.rules, or registered through extensions, run in every runtime view and report here.

EventFires whenPayload
onTriggerHandling / onTriggerHandleda trigger matched its event / finished{ eventName, triggerId, trigger, startedAt, actionCount } plus finishedAt, durationMs, executed, reason
onRuleEvaluating / onRuleEvaluateda rule is evaluated / finished{ runMode, ruleId, rule, startedAt, actionCount, otherwiseActionCount } plus executedBranch: 'actions' | 'otherwise' | 'skipped'

A valueChanged trigger reacts to values a person changes.

Example: a full save pipeline ​

ts
private readonly disposers: Array<() => void> = [];

ngOnInit(): void {
  this.disposers.push(
    this.api.onComplete.add(async ({ isValid, data }) => {
      if (!isValid) return;
      await firstValueFrom(this.http.post('/api/forms', data));
      this.api.markPristine();
      this.api.showToast({ title: 'Saved', variant: 'success' });
    }),
    this.api.onDataSourceLoadFailed.add((e) => this.logger.error('Data source failed', e.sourceName, e.errorMessage)),
  );
}

ngOnDestroy(): void {
  this.disposers.forEach((dispose) => dispose());
}