The form shown in the Edit Panel is built with the Studio form builder. It configures the data and format settings for both custom widgets and, through a Widget Override, the built-in widgets.
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgStudio } from "ag-studio-angular";
import {
AgDataEngine,
AgDataSourcesDefinition,
AgDefaultRegistry,
AgReportState,
AgStudioApi,
AgStudioApiReadyEvent,
AgStudioMode,
AgStudioProperties,
AgWidgetFormParams,
AgWidgetsConfig,
enableStudioDevValidations,
} from "ag-studio";
import { CustomDef, MyRegistry } from "./interfaces.ts";
import { createWidgets } from "ag-studio-angular";
import { CustomWidget } from "./custom-widget.component.ts";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
@Component({
selector: "my-app",
standalone: true,
imports: [AgStudio, CustomWidget],
template: `<div style="display: flex; flex-direction: column; height: 100%">
<ag-studio
style="width: 100%; height: 100%;"
class="my-studio-container"
[initialState]="initialState"
[widgets]="widgets"
[mode]="mode"
[data]="data"
(apiReady)="onApiReady($event)"
/>
</div> `,
})
export class AppComponent {
initialState: AgReportState<MyRegistry> = {
pages: [
{
id: "page1",
widgets: {
"1": {
type: "customWidget",
dataMapping: {
value: [
{
id: "medals.gold",
aggregation: "sum",
},
],
},
},
},
widgetLayout: {
"1": {
xTrack: 0,
yTrack: 0,
xSpan: 24,
ySpan: 12,
},
},
selection: {
type: "widget",
id: "1",
},
},
],
selectedPageId: "page1",
panels: {
filters: {
collapsed: true,
},
data: {
collapsed: true,
},
},
};
widgets:
| AgWidgetsConfig<MyRegistry>
| ((
widgets: AgWidgetsConfig<AgDefaultRegistry>,
) => AgWidgetsConfig<MyRegistry>) = createWidgets<MyRegistry>({
additionalTypes: [
{
id: "customWidget",
icon: {
url: "https://www.ag-grid.com/studio/images/brandmark.svg",
},
label: "Custom Widget",
dataMapping: {
value: {
type: "field",
supportedRoles: ["numeric"],
requires: { cardinality: "one" },
required: true,
},
},
form: (params: AgWidgetFormParams<CustomDef>) => {
const defaultForm = params.createDefaults({
dataMappingItems: [
{
key: "value",
label: "Value",
},
],
});
defaultForm.items[0].items.push({
type: "section",
key: "customSection",
label: "Special Config",
items: [
{
type: "number",
id: "format.style.valueFontSize",
label: "Value Font Size",
defaultValue: 48,
},
],
});
return defaultForm;
},
comp: CustomWidget,
defaultSize: {
width: 400,
height: 300,
},
minSize: {
width: 200,
height: 100,
},
ai: {
description:
"Custom widget used for displaying values in an interesting way.",
},
},
],
menu: [
{
label: "Custom",
widgetIds: ["customWidget"],
},
],
});
mode: AgStudioMode = "edit";
constructor(private http: HttpClient) {}
onApiReady(params: AgStudioApiReadyEvent) {
this.http
.get("https://www.ag-grid.com/studio/example-assets/olympic-winners.json")
.subscribe((data) => (this.data = { sources: [{ id: "medals", data }] }));
}
}
.custom-widget-container {
display: grid;
grid-template-columns: 1fr 1fr;
height: 100%;
}
.custom-widget-desc {
margin: 16px;
font-size: 24px;
align-content: center;
}
.custom-widget-value {
margin: 16px;
border: 2px solid var(--ag-border-color);
border-radius: var(--ag-border-radius);
text-align: center;
align-content: center;
font-weight: bold;
font-size: var(--custom-widget-value-font-size, 48px);
}
import '@angular/compiler';
import { provideHttpClient } from '@angular/common/http';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component.ts';
const app = bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
import type { AgBaseRegistry, AgBaseWidgetDefinition, AgWidgetDataFormat, AgWidgetFieldReference } from 'ag-studio';
interface CustomWidgetStyle {
valueFontSize?: number;
}
export interface CustomDef {
type: 'customWidget';
dataMapping: {
value: AgWidgetFieldReference[];
};
format?: AgWidgetDataFormat<CustomWidgetStyle>;
}
export interface MyRegistry extends AgBaseRegistry {
widgets: readonly AgBaseWidgetDefinition<'customWidget', CustomDef>[];
}
import type { WritableSignal } from '@angular/core';
import { Component, signal } from '@angular/core';
import type { AgWidgetParams } from 'ag-studio';
import type { AgCustomComponent } from 'ag-studio-angular';
import type { CustomDef } from './interfaces.ts';
@Component({
standalone: true,
template: `<div class="custom-widget-container" [style.--custom-widget-value-font-size]="valueFontSize()">
<div class="custom-widget-desc">Custom widget</div>
<div class="custom-widget-value">{{ value() }}</div>
</div>`,
})
export class CustomWidget implements AgCustomComponent<AgWidgetParams<CustomDef>> {
value = signal('');
valueFontSize: WritableSignal<string | null> = signal(null);
private hasLoaded = false;
agInit(params: AgWidgetParams<CustomDef>): Promise<void> {
return this.updateData(params);
}
refresh(params: AgWidgetParams<CustomDef>): Promise<void> {
return this.updateData(params);
}
private async updateData({ widgetApi, dataMapping, format }: AgWidgetParams<CustomDef>): Promise<void> {
const valueFontSize = format?.style?.valueFontSize;
this.valueFontSize.set(valueFontSize ? `${valueFontSize}px` : null);
const field = dataMapping.value?.at(0);
if (!field) {
widgetApi.setDisplayState('incompleteDataMapping');
return;
}
widgetApi.setDisplayState('loading', { prominent: !this.hasLoaded });
const response = await widgetApi.getData({ fields: [field] });
const data = response.results.rows;
const value = data.at(0)?.[field.key];
const hasData = value != null;
this.hasLoaded = hasData;
this.value.set(widgetApi.formatFieldValue(field, value));
widgetApi.setDisplayState(hasData ? 'displayed' : 'noData');
}
}
The example above adds a Special Config section to the setup tab, with an input to control the font size of the value in the custom widget.
The form is provided via the form property of the widget definition.
Form configuration using typed form builder.
|
Form Setup Copy Link
The ID of each form item is a dot delimited path of the corresponding property within the widget config.
interface CustomWidgetStyle {
valueFontSize?: number;
}
interface CustomWidgetDef {
type: 'customWidget';
dataMapping: {
value: AgWidgetFieldReference[];
};
format?: AgWidgetDataFormat<CustomWidgetStyle>;
}For the above definition, the corresponding input ID for the valueFontSize property would be format.style.valueFontSize.
Form Helpers Copy Link
The form callback provides some helper functions to create form elements for the default properties (e.g. data mapping, titles, etc.).
const widgetDefinition = {
// ...
form: (params) => {
return params.createDefaults({
dataMappingItems: [
{
key: 'value',
label: 'Value',
},
],
});
},
} Create the default tab group with a setup and format tab. Setup tab contains: |
Creates a section containing the widget type selector.
|
Create the data mapping item. Either a section if multiple data mapping fields, or a single fieldset or field item.
|
Creates a section containing the cross filter input.
|
Creates the title section containing the title, subtitle and caption group.
|
Creates the widget appearance section, containing the background, corner radius and border controls. The built-in widgets place it in the format tab, directly below the titles section. The widget's formatShape must declare a widget property of type AgWidgetAppearance for the section to work - a format shape drops the fields it does not declare, so a widget that omits it shows controls whose values are forgotten on the next reload.
|
Form Default Values Copy Link
Each form item has a default value assigned. This is what you see when there is no state set. When using the reset to default button for a section, these are the values that the form section will be reset to (rather than those in initialState/the last state set).
Update the form items to change the default values.
Form Grouping Items Copy Link
The following form items allow for grouping/structuring the form items (e.g. they have children).
| Item | Interface | Description |
|---|---|---|
| Tab Group | AgWidgetFormTabGroup / AgFormTabGroup | A group of tab items. E.g. the top-level Setup / Format tab group in the edit panel for the default widgets. |
| Tab | AgWidgetFormTab / AgFormTab | A child tab of a tab group item. E.g. the Setup tab in the edit panel for the default widgets. |
| Section | AgWidgetFormSection / AgFormSection | A top-level collection of items. E.g. the Titles section in the edit panel for the default widgets. |
| Group | AgWidgetFormGroup / AgFormGroup | A lower-level collection of items (with an optional toggle). E.g. the Title group in the edit panel for the default widgets. |
Form Input Items Copy Link
| Item | Interface | Description |
|---|---|---|
| Select | AgWidgetFormSelect / AgFormSelect | A select input item. |
| Checkbox | AgWidgetFormCheckbox / AgFormCheckbox | A checkbox input item. |
| Toggle | AgWidgetFormToggle / AgFormToggle | A toggle input item. |
| Text Input | AgWidgetFormTextField / AgFormTextField | A text input item. |
| Text Area | AgWidgetFormTextArea / AgFormTextArea | A text area input item. |
| Number Input | AgWidgetFormNumber / AgFormNumber | A number input item. |
| Optional Number Input | AgWidgetFormOptionalNumber / AgFormOptionalNumber | A number input item that allows optional values. |
| Color Input | AgWidgetFormColor / AgFormColor | A color picker input item. |
| Widget Type Selector | AgWidgetFormWidgetType (widget form only) | A select input that allows changing the widget type. |
| Field Selection Input | AgWidgetFormField (widget form only) | An input for selecting a field (supporting drag and drop). |
| Fieldset Selection Input | AgWidgetFormFieldSet (widget form only) | An input for selecting multiple fields (supporting drag and drop). |
| Grouped Typography Input | AgWidgetFormTypography (widget form only) | A group of elements for configuring typography. |