Skip to content

Creating Custom Widgets

While Valeros provides built-in widgets for common use cases, you can create custom widgets for domain-specific or specialized data presentation needs.

When to Create a Custom Widget

Create a custom widget when:

  • You need specialized rendering logic for a specific data type
  • Built-in widgets don't provide the functionality you need

For simple customization, consider using widget options with existing widgets first.

Creating a Widget Component

1. Create the Component

Create a new Angular component in src/app/widgets/. The default structure organizes domain-specific widgets in domain-specific/ and reusable widgets in generic/, but you can use any structure you prefer:

src/app/widgets/
├── domain-specific/
│   └── your-widget/
│       ├── your-widget.component.ts
│       └── your-widget.component.html
└── generic/
    └── another-widget/
        ├── another-widget.component.ts
        └── another-widget.component.html

2. Extend BaseWidget

Your component must extend BaseWidget to access the widget system's core functionality:

ts
import { Component, computed, Signal } from '@angular/core';
import { BaseWidget } from '../../base-widget';

@Component({
  selector: 'app-your-widget',
  imports: [],
  templateUrl: './your-widget.component.html',
})
export class YourWidget extends BaseWidget {
  // Your custom logic here
}

3. Use BaseWidget Properties

BaseWidget provides these computed properties:

  • node() - The current heritage object

  • property() - The property name this widget is rendering (e.g., "creator")

  • options() - Configuration options passed from the widget config (e.g., { maxLength: 200, largeFont: true, ... }, see Widget Options)

  • values() - Array of property values (e.g., [{ id: "...", type: "Person", name: "John Doe" }, ...])

  • showPropertyLabel() - Whether to show the property label

  • propertyLabel() - The label text to display

Property Labels Are Automatic

You don't need to manually render propertyLabel() or check showPropertyLabel() in your widget template. The widget system automatically wraps your widget with <app-property-label-wrapper> which handles displaying the label, icon, and layout based on the widget's options. Your widget template should only focus on rendering the actual content. See DynamicWidgetComponent for implementation details.

4. Implement Custom Logic

Use Angular signals to create derived state from the base properties. Example for formatting address data:

ts
export class AddressWidget extends BaseWidget {
  addresses = computed<FormattedAddress[]>(() => {
    const rawAddresses = this.values() as PostalAddress[];
    return rawAddresses.map((address) => ({
      ...address,
      postalInfo: [address.postalCode, address.addressLocality]
        .filter(Boolean)
        .join(' '),
      regionInfo: [address.addressRegion, address.addressCountry]
        .filter(Boolean)
        .join(', '),
    }));
  });
}

5. Create the Template

Create your template using the base properties.

html
@for (address of addresses(); track $index) {
<div>
  @if (address.streetAddress) {
  <div>{{ address.streetAddress }}</div>
  } @if (address.postalInfo) {
  <div>{{ address.postalInfo }}</div>
  } @if (address.regionInfo) {
  <div>{{ address.regionInfo }}</div>
  }
</div>
}

6. Register the Widget

Add your widget to the registry in src/app/config/presentation/widgets/widget-component.registry.ts:

ts
import { YourWidget } from '../../../widgets/domain-specific/your-widget/your-widget.component';

export const WIDGET_COMPONENT_REGISTRY = {
  'text-widget': TextWidget,
  'link-widget': LinkWidget,
  // ... other widgets
  'your-widget': YourWidget, // Add your widget
} as const;

7. Use in Configuration

Now you can use your widget in any presentation configuration:

ts
export const yourWidget: Widget = {
  id: 'your-widget',
  properties: ['yourProperty'],
  componentId: 'your-widget',
  options: {
    propertyLabel: 'Your Property',
    icon: 'star',
  },
};

export const DETAILS_PRESENTATION_CONFIG: NodePresentationConfig = {
  widgets: [yourWidget /* ... */],
  displayedWidgetIds: ['your-widget' /* ... */],
  // ...
};

Custom Widget Options

To add component-specific options, extend WidgetOptions (see TextWidget for an example):

ts
import { WidgetOptions } from '../../core/types/node-presentation-config';

export interface YourWidgetOptions extends WidgetOptions {
  customOption?: string;
  anotherOption?: boolean;
}

export class YourWidget extends BaseWidget {
  customOptions = computed(() => this.options() as YourWidgetOptions);

  customValue = computed(() => {
    return this.customOptions().customOption ?? 'default';
  });
}

Then use these options in your widget configuration with an explicit type cast:

ts
{
  id: 'your-widget',
  properties: ['yourProperty'],
  componentId: 'your-widget',
  options: {
    propertyLabel: 'Your Property',
    customOption: 'special-value',
    anotherOption: true,
  } as YourWidgetOptions,
}

Released under the EUPL-1.2 License.