Introduction to view components and view actions


View components and view actions are the main building blocks of a view. The views can be created and edited in the view designer which is part of the BMC Helix Innovation Studio.

The view components are implemented as regular Angular components. They extend classes, implement interfaces, and use services provided in BMC Helix Innovation Studio SDK. To make a view component available in the View designer, it must be registered using RxViewComponentRegistryService.

The view actions can be executed when a user interacts with a view component, e.g. when an action button is clicked, or a grid row action is activated.

The view actions are implemented as regular Angular services. Each runtime view action service must implement the execute method. To make a view action available in the view designer, it must be registered using RxViewActionRegistryService. 

Follow the information in the sections below to learn more about view components and view actions.

Types of view components

There are two types of view components that can be created: standalone, and record editor fields.

A standalone view component can be used by itself, while a record editor field view component is designed to be used within a record editor and is mapped to one of record's fields,

For most view components, there are two Angular components created: a design time component and a runtime component.

The design time component is used by the view designer. This component is displayed on the view designer canvas. It is responsible for collecting, validating, and persisting the view component configuration properties.

The runtime component is used by the application. This is the component with which the end users interact.

Before you begin

Make sure the view components are a part of a coded application, or a library. To learn how to create an application, or a library, please visit this Creating-a-Project-using-Maven-and-the-Archetype.

Creating view components

View components can be created by using Angular schematics provided in the BMC Helix Innovation Studio SDK.

  • To create a standalone view component, navigate to the project's webapp directory located at <bundle-name>/bundle/src/main/webapp, and run the following command:
npx ng g rx-view-component <view-component-name>
  • To create a record editor field view component, navigate to the webapp directory, and run the following command:
npx ng g rx-field-view-component <view-component-name>

Standalone view components

In this section, we will take a closer look at the parts of a standalone view component created by using the following command:

npx ng g rx-view-component foo

This command will create the files that contain the boilerplate code of a simple standalone view component.

image-2024-5-1_14-56-22.png

The files are divided into two directories: design, and runtime. There is also a registration module and some common files located in the root directory.

The design directory contains the implementation of the design time view component, while the runtime directory is where you will find the runtime view component implementation.

Below is the list of all files that a standalone view component comprises.

File name

File description

<view-component-name>.types.ts

A TypeScript interface that describes view component's runtime properties.

<view-component-name>-registration.module.ts

An Angular module required for registering the view component.

/design

The design time view component implementation

<view-component-name>-design.component.html

The HTML template of the design time view component.

<view-component-name>-design.component.scss

CSS styles applied to the design time view component.

<view-component-name>-design.component.ts

The design time view component.

<view-component-name>-design.model.ts

An Angular service that defines the design time view component model.

<view-component-name>-design.types.ts

A TypeScript interface that describes design time view component's properties.

/runtime

The runtime view component implementation.

<view-component-name>.component.html

The HTML template of the runtime view component.

<view-component-name>.component.scss

CSS styles applied to the runtime view component.

<view-component-name>.component.ts

The runtime view component.

Code walkthrough

The view components are created in the view-components directory:

Creating standalone view component
$ npx ng g rx-view-component foo
CREATE libs/com-example-myapp/src/lib/view-components/foo/foo-registration.module.ts (918 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/foo.types.ts (164 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/design/foo-design.component.html (40 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/design/foo-design.component.scss (97 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/design/foo-design.component.ts (496 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/design/foo-design.model.ts (5687 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/design/foo-design.types.ts (170 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/runtime/foo.component.html (52 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/runtime/foo.component.scss (97 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo/runtime/foo.component.ts (1975 bytes)
UPDATE libs/com-example-myapp/src/lib/com-example-myapp.module.ts (813 bytes)
  • foo-registration.module.ts

FooRegistrationModule is defined in this file. In the constructor of this Angular module, the view component gets registered to make it available in the view designer and in the applications.

RxViewComponentRegistryService#register method is passed a view component descriptor object. Some of its most used properties are described below:

Property

Description

type

A string that identifies the view component. To make it unique, the name of the view component is prefixed with the owner bundle ID. This string is used as the name of a DOM element that represents this view component in an application. This string is also saved in the view definition as the identifier of the view component.

name

A string that is used in the View designer to describe the view component. For example, in the palette, in the breadcrumb, in the expression builder data dictionary, and so on.

group

A string that identifies the section in the view designer palette where this view component will be placed. If not specified, this property defaults to owner bundle friendly name.

icon

The name of the icon to display in the View designer palette.

component

An Angular component that implements the runtime view component.

designComponent

An Angular component that implements the design time view component.

designComponentModel

An Angular service responsible for design time behavior of the view component, including initialization, property inspector configuration, property validation, data dictionary construction, and so on.

properties

An array of component property descriptor objects with the following properties:

  • name: the name of the property as saved in the view definition.
  • label: a user-friendly name for the property. It is used as a label for the field in the view component property inspector, as a property name in the validation error messages, etc.
  • type: the data type of the property as used at runtime, for example, boolean, string, number, array, or object. If specified, the runtime will try to cast the property's value to the provided type.
  • designType: the data type of the property as used in the view designer, e.g. boolean, string, number, array, or object. If specified, the view designer will try to cast the value of the property to the provided type.
  • enableExpressionEvaluation: a boolean that indicates whether the property value should be evaluated at runtime as an expression, as opposed to being treated as a raw value.
  • evaluatorService: an Angular service that implements the custom expression evaluation logic.

  • localizable: a boolean that indicates whether the property value has to be localized.

The standard properties (Hidden, CSS classes, and Available on devices) are added to the list of the view component properties by using RX_STANDARD_PROPS_DESC.  These are the properties that are available for most view components.

foo-registration.module.ts
import { NgModule } from '@angular/core';
import { RX_STANDARD_PROPS_DESC, RxViewComponentRegistryService } from '@helix/platform/view/api';
import { FooDesignComponent } from './design/foo-design.component';
import { FooDesignModel } from './design/foo-design.model';
import { FooComponent } from './runtime/foo.component';

@NgModule()
export class FooRegistrationModule {
  constructor(rxViewComponentRegistryService: RxViewComponentRegistryService) {
    rxViewComponentRegistryService.register({
      type: 'com-example-myapp-foo',
      name: 'Foo',
      group: 'MyApp',
      icon: 'wall',
      component: FooComponent,
      designComponent: FooDesignComponent,
      designComponentModel: FooDesignModel,
      properties: [
        {
          name: 'message',
          localizable: true,
          enableExpressionEvaluation: true
        },
        ...RX_STANDARD_PROPS_DESC
      ]
    });
  }
}
  • foo.types.ts

IFooProperties interface is defined in this file. It describes the runtime properties of the view component.

This interface extends IRxStandardProps, which describes the standard view component properties (hidden , styles , availableOnDevices).

foo.types.ts
import { IRxStandardProps } from "@helix/platform/view/api";

export interface IFooProperties extends IRxStandardProps {
  name: string;
  message: string;
}
  • foo-design.component.ts

This file contains the Angular component that implements the design time view component.

In this example, it's a simple class with a single model property.

design/foo-design.component.ts
import { Component, Input } from '@angular/core';
import { FooDesignModel } from './foo-design.model';

@Component({
  standalone: true,
  selector: 'com-example-myapp-foo-design',
  styleUrls: ['./foo-design.component.scss'],
  templateUrl: './foo-design.component.html'
})
export class FooDesignComponent {
 @Input()
  model: FooDesignModel;
}
  • foo-design.component.html

This file contains the HTML template for the design time view component. It defines how the view component will be rendered on the view designer canvas.

design/foo-design.component.html
<p>Message will be displayed here.</p>
  • foo-design.component.scss

This file contains the CSS rules to apply to the design time view component.

All CSS rules are defined by using SASS language.

In this example, $color-background SASS variable is imported from styles/variables and then used to set the (light gray) background color of the design time view component. By using SASS variables helps view components look more consistent and be theming-ready.

design/foo-design.component.scss
@import "styles/variables";

p {
 background-color: $color-background;
 padding: 10px;
}
  • foo-design.model.ts

This file contains the FooDesignModel class that implements the view component design model. 

This class has multiple responsibilities. It helps the design time view component integrate into the view designer environment.

Refer to the comments in the code below for more details.

design/foo-design.model.ts
import { Injector } from '@angular/core';
import { Tooltip } from '@helix/platform/shared/api';
import {
  ExpressionFormControlComponent,
  IExpressionFormControlOptions,
  TextFormControlComponent
} from '@helix/platform/shared/components';
import { IViewDesignerComponentModel, RX_STANDARD_PROPS_DEFAULT_VALUES } from '@helix/platform/view/api';
import {
  getStandardPropsInspectorConfigs,
  IViewComponentDesignCommonDataDictionaryBranch,
  IViewComponentDesignSandbox,
  IViewComponentDesignValidationIssue,
  validateStandardProps,
  ViewDesignerComponentModel
} from '@helix/platform/view/designer';
import {
  IViewComponentDesignSettablePropertiesDataDictionary
} from '@helix/platform/view/designer/public-interfaces/view-component-design-settable-properties-data-dictionary.interfaces';
import { takeUntil } from 'rxjs/operators';
import { IFooProperties } from '../foo.types';
import { IFooDesignProperties } from './foo-design.types';

const initialComponentProperties: IFooProperties = {
  name: '',
  message: ''
};

export class FooDesignModel extends ViewDesignerComponentModel<IFooProperties, IFooDesignProperties> implements IViewDesignerComponentModel<IFooProperties, IFooDesignProperties> {
  constructor(protected injector: Injector,
             protected sandbox: IViewComponentDesignSandbox<IFooDesignProperties>) {
   super(injector, sandbox);

   // Configure view component property inspector.
   sandbox.updateInspectorConfig(this.setInspectorConfig(initialComponentProperties));

   // Validate view component properties.
   // Note that the standard view component properties should also be validated.
   this.sandbox.componentProperties$
      .pipe(takeUntil(this.sandbox.destroyed$))
      .subscribe((properties: IFooDesignProperties) => {
       this.sandbox.setValidationIssues(this.validate(this.sandbox, properties));
      });

   this.sandbox.getComponentPropertyValue('name').subscribe((name) => {
     const componentName = name ? `${this.sandbox.descriptor.name} (${name})` : this.sandbox.descriptor.name;

     // Add settable view component properties to the expression builder data dictionary.
     // These properties can be set via the Set property view action.
     this.sandbox.setSettablePropertiesDataDictionary(componentName, this.getSettablePropertiesDataDictionaryBranch());

     // Add view component properties to the expression builder data dictionary.
     // The values of these properties can be used when building expressions.
     this.sandbox.setCommonDataDictionary(this.prepareDataDictionary(componentName));
    });
  }

 // This method is called when a new, or an existing view component is initialized in the view designer.
 // It returns values for all properties of the view component.
 static getInitialProperties(currentProperties?: IFooProperties): IFooDesignProperties {
   return {
     // initial values for custom properties
     name: '',
      message: '',
     // initial values for the standard properties available for all view components
     ...RX_STANDARD_PROPS_DEFAULT_VALUES,
     // property values of an existing view component that are already saved in the view
     ...currentProperties
    };
  }

 private getSettablePropertiesDataDictionaryBranch(): IViewComponentDesignSettablePropertiesDataDictionary {
   return [
      {
        label: 'Hidden',
        expression: this.getExpressionForProperty('hidden')
      },
      {
        label: 'Message',
        expression: this.getExpressionForProperty('message')
      }
    ];
  }

 private prepareDataDictionary(componentName: string): IViewComponentDesignCommonDataDictionaryBranch {
   return {
      label: componentName,
      children: [
        {
          label: 'Message',
          expression: this.getExpressionForProperty('message')
        }
      ]
    };
  }

 private setInspectorConfig(model: IFooProperties) {
   return {
      inspectorSectionConfigs: [
        {
          label: 'General',
          controls: [
            {
              name: 'name',
              component: TextFormControlComponent,
              options: {
                label: 'Name',
                tooltip: new Tooltip('Enter a name to uniquely identify this view component.')
              }
            },
            {
              name: 'message',
              component: ExpressionFormControlComponent,
              options: {
                label: 'Message',
                tooltip: new Tooltip('The Message is a required property whose value will be displayed at runtime.'),
                dataDictionary$: this.expressionConfigurator.getDataDictionary(),
                operators: this.expressionConfigurator.getOperators(),
                isRequired: true
              } as IExpressionFormControlOptions
            },
           // Add standard properties available for most view components, such as
           // Hidden, Available on devices, CSS classes.
           ...getStandardPropsInspectorConfigs()
          ]
        }
      ]
    };
  }

 private validate(
    sandbox: IViewComponentDesignSandbox<IFooDesignProperties>,
    model: IFooDesignProperties
  ): IViewComponentDesignValidationIssue[] {
   const validationIssues: IViewComponentDesignValidationIssue[] = [];

   if (!model.message) {
      validationIssues.push(sandbox.createError('Message cannot be blank.', 'message'));
    }

   // Validate standard properties.
   validationIssues.push(...validateStandardProps(model));

   return validationIssues;
  }
}
  • foo-design.types.ts

IFooDesignProperties interface is defined in this file. It describes the design time properties of the view component.

This interface extends IRxStandardProps, which describes the standard view component properties (hidden, styles , availableOnDevices).

design/foo-design.types.ts
import { IRxStandardProps } from '@helix/platform/view/api';

export interface IFooDesignProperties extends IRxStandardProps {
  name: string;
  message: string;
}
  • foo.component.ts

This file contains the Angular component that implements the runtime view component.

It inherits the base view component functionality from BaseViewComponent. 

@RxViewComponent decorator must be applied to the runtime view component class. It allows the applications to determine the owner bundle of the view component and load that bundle when needed.

For more information, see the following code:

runtime/foo.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { RxViewComponent } from '@helix/platform/view/api';
import { BaseViewComponent, IViewComponent } from '@helix/platform/view/runtime';
import { Observable, throwError } from 'rxjs';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';
import { IFooProperties } from '../foo.types';

@Component({
  standalone: true,
  selector: 'com-example-myapp-foo',
  styleUrls: ['./foo.component.scss'],
  templateUrl: './foo.component.html'
})
@RxViewComponent({
  name: 'com-example-myapp-foo'
})
export class FooComponent extends BaseViewComponent implements OnInit, IViewComponent {
 @Input()
  config: Observable<IFooProperties>;

  api = {
   // This method will be called when a component property is set via the Set property view action.
   setProperty: this.setProperty.bind(this)
  };

 protected state: IFooProperties;

  ngOnInit() {
   super.ngOnInit();

   // Make component API available to runtime view.
   this.notifyPropertyChanged('api', this.api);

   // Subscribe to configuration property changes.
   this.config.pipe(distinctUntilChanged(), takeUntil(this.destroyed$)).subscribe((config: IFooProperties) => {
     // Setting isHidden property to true will remove the component from the DOM.
     this.isHidden = Boolean(config.hidden);

     this.state = { ...config };
    });
  }

 private setProperty(propertyPath: string, propertyValue: any): void | Observable<never> {
   switch (propertyPath) {
     case 'hidden': {
       this.state.hidden = propertyValue;
       this.notifyPropertyChanged(propertyPath, propertyValue);
       break;
      }
     case 'message': {
       this.state.message = propertyValue;
       this.notifyPropertyChanged(propertyPath, propertyValue);
       break;
      }
     default: {
       return throwError(`Foo : property ${propertyPath} is not settable.`);
      }
    }
  }
  • foo.component.html

This file contains the HTML template for the runtime view component. It defines how the view component will be rendered in the application.

runtime/foo.component.html
<p>
  {{state.message ?? '[No message]'}}
</p>
  • foo.component.scss

This file contains the CSS rules to apply to the runtime view component.

All CSS rules are defined using SASS language.

In this example, a $color-background SASS variable is imported from styles/variables and then used to set the (light gray) background color of the view component. By using SASS variables helps view components look more consistent and be theming-ready.

runtime/foo.component.scss
@import "styles/variables";

p {
 background-color: $color-background;
 padding: 10px;
}

Record editor field view components

In this section, we will take a closer look at the parts of a record editor field view component created by using the following command:

npx ng g rx-field-view-component foo-field

This command will create the files that contain the boilerplate code of a simple record editor field view component.

image-2024-5-6_12-55-45.png

The files are divided into two directories: design, and runtime. The registration module is located in the root directory.

The design directory contains the implementation of the design time view component, while the runtime directory is where you will find the runtime view component implementation.

Below is the list of all files that a record editor field view component comprises.

File name

File description

<view-component-name>-registration.module.ts

An Angular module required for registering the view component.

/design

The design time view component implementation.

<view-component-name>-design.component.html

The HTML template of the design time view component.

<view-component-name>-design.component.scss

CSS styles applied to the design time view component.

<view-component-name>-design.component.ts

The design time view component.

<view-component-name>-design.model.ts

An Angular service that defines the design time view component model.

<view-component-name>-design.types.ts

A TypeScript interface that describes design time view component's properties.

/runtime

The runtime view component implementation.

<view-component-name>.component.html

The HTML template of the runtime view component.

<view-component-name>.component.scss

CSS styles applied to the runtime view component.

<view-component-name>.component.ts

The runtime view component.

<view-component-name>.types.ts

A TypeScript interface that describes runtime view component's properties.

Code walkthrough

  • The view components are created in the view-components directory:
Creating record editor field view component
$ npx ng g rx-field-view-component foo-field
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/foo-field-registration.module.ts (1674 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/design/foo-field-design.component.html (261 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/design/foo-field-design.component.scss (97 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/design/foo-field-design.component.ts (625 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/design/foo-field-design.model.ts (4054 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/design/foo-field-design.types.ts (198 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/runtime/foo-field.component.html (749 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/runtime/foo-field.component.scss (97 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/runtime/foo-field.component.ts (1591 bytes)
CREATE libs/com-example-myapp/src/lib/view-components/foo-field/runtime/foo-field.types.ts (209 bytes)
UPDATE libs/com-example-myapp/src/lib/com-example-myapp.module.ts (620 bytes)
  • foo-field-registration.module.ts

FooFieldRegistrationModule is defined in this file. In the constructor of this Angular module, the view component gets registered to make it available in the view designer and in the applications.

RxViewComponentRegistryService#register method is passed a view component descriptor object. Some of its most used properties are described below:

Property

Description

type

A string that identifies the view component. To make it unique, the name of the view component is prefixed with the owner bundle ID. This string is used as the name of a DOM element that represents this view component in an application. This string is also saved in the view definition as the identifier of the view component.

name

A string used in the View designer to describe the view component, such as in the palette, the breadcrumb, the expression builder data dictionary, and so on.

icon

The name of the icon to display in the View designer palette.

group

A string that identifies the section in the view designer palette where this view component will be placed. If not specified, this property defaults to owner bundle friendly name.

options

An object with additional configuration options such as canBeEmbeddedInRecordEditor: a boolean that indicates whether the view component can be shown inside a record editor.

canBeInsertedInto

A method that determines whether this view component can be dropped inside another view component on the view designer canvas. This method receives an array of component types that includes the target view component and all of its parents.

component

An Angular component that implements the runtime view component.

designComponent

An Angular component that implements the design time view component.

designComponentModel

An Angular service responsible for design time behavior of the view component, including initialization, property inspector configuration, property validation, data dictionary construction.

properties

An array of component property descriptor objects with the following properties:

  • name: the name of the property as saved in the view definition.
  • enableExpressionEvaluation: a boolean that indicates whether the property value should be evaluated at runtime as an expression, as opposed to being treated as a raw value.

The standard record editor field properties are added to the list of the view component properties using RX_BASE_FIELD_PROPERTIES. These are the properties that are available for all record editor field components:

  • api: the API of the view component
  • disabled: a boolean that Indicates if the view component should be disabled.
  • recordDefinition: the record definition to which the parent record editor is bound.
  • recordInstance: the record instance displayed in the parent record editor.
  • hidden: a boolean that Indicates if the view component should be hidden.
  • inReadState: a boolean that indicates if the parent record editor is in read state
  • value: an expression for the value of the field. It can be used for calculated properties.
foo-field-registration.module.ts
import { NgModule } from '@angular/core';
import { RxViewComponentRegistryService, RxViewComponentType } from '@helix/platform/view/api';
import { RX_BASE_FIELD_PROPERTIES } from '@helix/platform/view/components';
import { FooFieldDesignComponent } from './design/foo-field-design.component';
import { FooFieldDesignModel } from './design/foo-field-design.model';
import { FooFieldComponent } from './runtime/foo-field.component';

@NgModule()
export class FooFieldRegistrationModule {
  constructor(rxViewComponentRegistryService: RxViewComponentRegistryService) {
    rxViewComponentRegistryService.register({
      type: 'com-example-myapp-foo-field',
      name: 'Foo Field',
      icon: 'wall',
      group: 'MyApp',
      options: {
       // Specify that the view component can be placed inside a record editor.
       // If this property is not set, the Record editor will not accept the view component.
       canBeEmbeddedInRecordEditor: true
      },
     // Do not allow the view component to be placed inside a view component
     // other than the Record editor.
     canBeInsertedInto(componentTypes: string[]): boolean {
       return componentTypes.includes(RxViewComponentType.RecordEditor);
      },
      component: FooFieldComponent,
      designComponent: FooFieldDesignComponent,
      designComponentModel: FooFieldDesignModel,
     // An array of view component properties.
     // Note, that the custom properties must be combined with the base field properties.
     properties: [...RX_BASE_FIELD_PROPERTIES, {
        name: 'message',
        enableExpressionEvaluation: true
      }]
    });
  }
}
  • foo-field-design.component.html

This file contains the HTML template for the design time view component. It defines how the view component will be rendered on the view designer canvas.

design/foo-field-design.component.html
<p>The configured message will be displayed here.</p>

<adapt-rx-textfield [required]="model.isRequired$ | async"
                   [label]="model.label$ | async"
                   [disabled]="true"
                   ngModel
></adapt-rx-textfield>
  • foo-field-design.component.scss

This file contains the CSS rules to apply to the design time view component.

All CSS rules are defined using SASS language.

In this example, $color-background SASS variable is imported from styles/variables and then used to set the (light gray) background color of the design time view component. By using SASS variables helps view components look more consistent and be theming-ready.

design/foo-field-design.component.scss
@import "styles/variables";

p {
 background-color: $color-background;
 padding: 10px;
}
  • foo-field-design.component.ts

This file contains the Angular component that implements the design time view component.

In this example, it's a simple class with a single model property.

design/foo-field-design.component.ts
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AdaptRxTextfieldModule } from '@bmc-ux/adapt-angular';
import { FooFieldDesignModel } from './foo-field-design.model';

@Component({
  standalone: true,
  selector: 'com-example-myapp-foo-field-design',
  styleUrls: ['./foo-field-design.component.scss'],
  templateUrl: './foo-field-design.component.html',
  imports: [CommonModule, FormsModule, AdaptRxTextfieldModule]
})
export class FooFieldDesignComponent {
 @Input()
  model: FooFieldDesignModel;
}
  • foo-field-design.model.ts

This file contains the FooFieldDesignModel class that implements the view component design model. This class has multiple responsibilities. It helps the design time view component integrate into the view designer environment and into the record editor design view component.

Refer to the comments in the code below for more details.

design/foo-field-design.model.ts
import { Injector } from '@angular/core';
import { RX_RECORD_DEFINITION } from '@helix/platform/record/api';
import { Tooltip } from '@helix/platform/shared/api';
import { ExpressionFormControlComponent, IExpressionFormControlOptions } from '@helix/platform/shared/components';
import { BaseRecordEditorFieldDesign } from '@helix/platform/view/components';
import {
  IViewComponentDesignSandbox,
  IViewComponentDesignValidationIssue,
  IViewDesignerInspectorConfig
} from '@helix/platform/view/designer';
import {
  IViewDesignerInspectorSectionConfig
} from '@helix/platform/view/designer/public-interfaces/view-designer-inspector.types';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { IFooFieldDesignProperties } from './foo-field-design.types';

export class FooFieldDesignModel extends BaseRecordEditorFieldDesign {
 // An array of record field types to which this view component can be bound.
 fieldResourceTypes = [RX_RECORD_DEFINITION.resourceTypes.character];

  componentProperties$: Observable<IFooFieldDesignProperties> = this.sandbox.componentProperties$;

  constructor(public injector: Injector, public sandbox: IViewComponentDesignSandbox<IFooFieldDesignProperties>) {
   super(injector, sandbox);

   // Validate custom view component properties.
   // Note that the standard properties are validated in the base class.
   combineLatest([this.sandbox.componentProperties$])
      .pipe(
        map(([componentProperties]: [IFooFieldDesignProperties]) =>
         this.validateCustomProperties(this.sandbox, componentProperties)
        )
      )
      .subscribe((validationIssues) => {
       this.sandbox.setValidationIssues(validationIssues);
      });
  }

 // This method is called when a new, or an existing view component is initialized in the view designer.
 // It returns values for all properties of the view component.
 static getInitialProperties(currentProperties?: IFooFieldDesignProperties): IFooFieldDesignProperties {
   return {
     // initial values for custom properties
     message: '',
     // initial values for the standard properties available for all view components
     ...BaseRecordEditorFieldDesign.getInitialProperties(),
     // property values of an existing view component that are already saved in the view
     ...currentProperties
    };
  }

 // Override to customize the configuration of the view component property inspector.
 getInspectorConfig(): Observable<IViewDesignerInspectorConfig> {
   return super.getInspectorConfig()
      .pipe(
        map(
          (inspectorConfig: IViewDesignerInspectorConfig) => this.buildInspectorConfig(inspectorConfig)
        )
      );
  };

 private buildInspectorConfig(defaultInspectorConfig: IViewDesignerInspectorConfig): IViewDesignerInspectorConfig {
   // Create a new section in the view component property inspector.
   const inspectorSectionConfig: IViewDesignerInspectorSectionConfig = {
      label: 'Custom',
      controls: [
        {
          name: 'message',
          component: ExpressionFormControlComponent,
          options: {
            label: 'Message',
            tooltip: new Tooltip('This message will be displayed above the label.'),
            dataDictionary$: this.expressionConfigurator.getDataDictionary(),
            operators: this.expressionConfigurator.getOperators(),
            isRequired: true
          } as IExpressionFormControlOptions
        }
      ]
    };

    defaultInspectorConfig.inspectorSectionConfigs.push(inspectorSectionConfig);

   return defaultInspectorConfig;
  }

 private validateCustomProperties(
    sandbox: IViewComponentDesignSandbox,
    model: IFooFieldDesignProperties
  ): IViewComponentDesignValidationIssue[] {
   const validationIssues = [];

   if (!model.message) {
      validationIssues.push(sandbox.createError('Message cannot be blank.', 'message'));
    }

   return validationIssues;
  }
}
  • foo-field-design.types.ts

IFooFieldDesignProperties interface is defined in this file. It describes the design-time properties of the view component.

This interface extends IBaseRecordEditorFieldProperties, which describes the standard record editor field view component properties:

  • fieldId
  • label 
  • value 
  • hidden
  • styles
  • availableOnDevices
  • disabled 
design/foo-field-design.types.ts
import { IBaseRecordEditorFieldProperties } from '@helix/platform/view/components';

export interface IFooFieldDesignProperties extends IBaseRecordEditorFieldProperties {
  message?: string;
}
  • foo-field.component.html

This file contains the HTML template for the runtime view component. It defines how the view component will be rendered in the application.

runtime/foo-field.component.html
<!-- This HTML will be used when the record editor is in read state. -->
<ng-container>
 <p>{{ (config | async).message }}</p>
 <rx-read-only-field
   *ngIf="inReadState; else editStateElementRef"
   [label]="label"
   [value]="getDisplayValue()"
 ></rx-read-only-field>
</ng-container>

<!-- This HTML will be used when the record editor is in edit state. -->
<ng-template #editStateElementRef>
 <adapt-rx-textfield [label]="label"
                     [formControl]="formControl"
                     [required]="isRequired"
                     [readonly]="isDisabled"
                     [disabledStyleForReadonlyState]="true"
                     [maxlength]="maxLength"
 ></adapt-rx-textfield>
</ng-template>
  • foo-field.component.scss

This file contains the CSS rules to apply to the runtime view component.

All CSS rules are defined by using SASS language.

In this example, a $color-background SASS variable is imported from styles/variables and then used to set the (light gray) background color of the view component. By using SASS variables helps view components look more consistent and be theming-ready.

runtime/foo-field.component.scss
@import "styles/variables";

p {
 background-color: $color-background;
 padding: 10px;
}
  • foo-field.component.ts

This file contains the Angular component that implements the runtime view component.

It inherits the base record editor field view component functionality from BaseRecordEditorFieldComponent.

@RxViewComponent decorator must be applied to the runtime view component class. It allows the applications to determine the owner bundle of the view component and load that bundle when needed.

For more information, see the following code:

runtime/foo-field.component.ts
import { CommonModule } from '@angular/common';
import { Component, Injector } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { AdaptRxTextfieldModule } from '@bmc-ux/adapt-angular';
import { RxFieldDefinitionService } from '@helix/platform/record/api';
import { ReadOnlyFieldModule } from '@helix/platform/ui-kit';
import { RxViewComponent } from '@helix/platform/view/api';
import { BaseRecordEditorFieldComponent } from '@helix/platform/view/components';
import { IViewComponent } from '@helix/platform/view/runtime';
import { IFooFieldRuntimeProperties } from './foo-field.types';

@Component({
  standalone: true,
  selector: 'com-example-myapp-foo-field',
  styleUrls: ['./foo-field.component.scss'],
  templateUrl: './foo-field.component.html',
  imports: [CommonModule, ReadOnlyFieldModule, AdaptRxTextfieldModule, ReactiveFormsModule]
})
@RxViewComponent({
  name: 'com-example-myapp-foo-field'
})
export class FooFieldComponent extends BaseRecordEditorFieldComponent implements IViewComponent {
 protected maxLength: number;

  constructor(injector: Injector,
             private rxFieldDefinitionService: RxFieldDefinitionService) {
   super(injector);
  }

 // Override to initialize internal component properties.
 onConfigInitialized(config: IFooFieldRuntimeProperties): void {
   super.onConfigInitialized(config);

   if (this.fieldDefinition.maxLength && !this.rxFieldDefinitionService.isSystemField(this.fieldDefinition)) {
     this.maxLength = this.fieldDefinition.maxLength;
    }
  }
}
  • foo-field.type.ts

IFooFieldRuntimeProperties interface is defined in this file. It describes the runtime properties of the view component.

This interface extends IBaseRecordEditorFieldComponentConfig, which describes the standard record editor field view component properties (hidden, styles, availableOnDevices).

runtime/foo-field.types.ts
import { IBaseRecordEditorFieldComponentConfig } from '@helix/platform/view/components';

export interface IFooFieldRuntimeProperties extends IBaseRecordEditorFieldComponentConfig {
  message?: string;
}

View actions

As with the view components, view actions have two sides of the implementation: runtime and design time.

At runtime, a view action is a service that implements the execute method. The method receives the view action parameters and returns an Observable that should be completed when the view action is completed. It can also throw an error if something goes wrong. When a view action throws an error, the following view actions will not be executed.

At design-time, a view action must provide the design manager and the design model services. These services will be responsible for configuring the view action in the view designer.

Creating view actions

Prerequisites

View actions must be part of a coded application, or a library.

To learn how to create an application, or a library, please visit this page.

View actions can be created with an Angular schematic provided in the Helix Platform SDK.

To create a view action, navigate to project's webapp directory (<bundle-name>/bundle/src/main/webapp) and run the following command:

npx ng g rx-view-action <view-action-name>

Exploring a view action

In this section, we will take a closer look at the parts of a view action created using the following command:

npx ng g rx-view-action foo

This command will create the files that contain the boilerplate code of a simple view action.

image-2024-5-6_16-23-33.png

Below is the list of all files that a view action comprises.

File name

File description

<view-action-name>-action.module.ts

An Angular module required for registering the view action.

<view-action-name>-action.service.ts

The runtime view action service.

<view-action-name>-action.types.ts

A TypeScript interface that describes the runtime view action parameters.

<view-action-name>-action-design.types.ts

A TypeScript interface that describes the design time view action parameters.

<view-action-name>-action-design-manager.service.ts

The design time view action manager service responsible for view action parameter validation.

<view-action-name>-action-design-model.class.ts

The design time view action model class responsible for view action initialization and configuration.

Code walkthrough

  • The view actions are created in the actions directory:
Creating view action
$ npx ng g rx-view-action foo
CREATE libs/com-example-myapp/src/lib/actions/foo/foo-action-design-manager.service.ts (727 bytes)
CREATE libs/com-example-myapp/src/lib/actions/foo/foo-action-design-model.class.ts (2184 bytes)
CREATE libs/com-example-myapp/src/lib/actions/foo/foo-action-design.types.ts (181 bytes)
CREATE libs/com-example-myapp/src/lib/actions/foo/foo-action.module.ts (1392 bytes)
CREATE libs/com-example-myapp/src/lib/actions/foo/foo-action.service.ts (1210 bytes)
CREATE libs/com-example-myapp/src/lib/actions/foo/foo-action.types.ts (64 bytes)
UPDATE libs/com-example-myapp/src/lib/com-example-myapp.module.ts (572 bytes)
  • foo-action.module.ts

FooActionModule is defined in this file. In the constructor of this Angular module, the view action gets registered to make it available in the view designer and in the applications.

RxViewActionRegistryService#register method is passed a view action descriptor object. Some of its most used properties are described below:

Property

Description

name

The name of the view action as saved in the view definition. To make it unique, always prefix it with the bundle name, similar to how it's done by the Angular schematic.

label

The name of the action that will be displayed in the view designer.

service

A service that implements the business logic of the view action.

designManager

The design manager service responsible for the view action parameter validation.

designModel

The class responsible for the view action design time behavior.

parameters

An array of view action parameter descriptor objects with the following properties:

  • name: the name of the parameter as saved in the view definition.
  • label: the name of the parameter that will be displayed in the view designer.
  • isRequired: a boolean that indicates if the view action parameter is required.
  • enableExpressionEvaluation: a boolean that indicates if the parameter value has to be evaluated as an expression at runtime, or if it should be treated as a raw value.


foo-action.module.ts
import { NgModule } from '@angular/core';
import { RxViewActionRegistryService } from '@helix/platform/view/api';
import { FooActionDesignManagerService } from './foo-action-design-manager.service';
import { FooActionDesignModel } from './foo-action-design-model.class';
import { FooActionService } from './foo-action.service';

@NgModule({
  providers: [FooActionService, FooActionDesignManagerService]
})
export class FooActionModule {
  constructor(
    rxViewActionRegistryService: RxViewActionRegistryService,
    fooActionService: FooActionService,
    fooActionDesignManagerService: FooActionDesignManagerService
  ) {
    rxViewActionRegistryService.register({
      name: 'comExampleMyappFoo',
      label: 'Foo',
     // a service that will execute the view action at runtime
     service: fooActionService,
      // the design manager service responsible for view action parameter validation at design time
     designManager: fooActionDesignManagerService,
     // the design model class responsible for the design time behavior of the view action
     designModel: FooActionDesignModel,
     // the list of view action input parameters
     parameters: [
        {
          name: 'message',
          label: 'Message',
          isRequired: true,
          enableExpressionEvaluation: true
        }
      ]
    });
  }
}
  • foo-action.service.ts

This service implements view action's business logic that will be executed at runtime.

@RxViewAction decorator must be applied to the view action service class. It will allow the view designer and the applications to determine the owner bundle of the view action and load it when needed.

foo-action.types.ts
import { Injectable } from '@angular/core';
import { RX_MODAL, RxModalService } from '@helix/platform/ui-kit';
import { IViewActionService, RxViewAction } from '@helix/platform/view/api';
import { EMPTY, from, Observable, throwError } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { IFooActionProperties } from './foo-action.types';

@Injectable()
@RxViewAction({
  name: 'comExampleMyappFoo'
})
export class FooActionService implements IViewActionService<IFooActionProperties, never> {
  constructor(private rxModalService: RxModalService) {
  }

 // Implements the runtime behavior of the view action.
 execute(inputParameters: IFooActionProperties): Observable<never> {
   return from(this.rxModalService.confirm({
      title: 'default',
      modalStyle: RX_MODAL.modalStyles.warning,
      message: inputParameters.message
    })).pipe(
     // Return a completed observable if confirmed, or throw error if rejected.
     // Throwing an error will cancel the following actions.
     switchMap((result: boolean) => {
       if (result) {
         return EMPTY;
        } else {
         return throwError(null);
        }
      })
    );
  }
}
  • foo-action.types.ts

This file contains an interface that describes view action parameters.

foo-action.types.ts
export interface IFooActionProperties {
  message: string;
}
  • foo-action-design.types.ts

This file contains an interface that describes view action's design time parameters.

This interface must extend IViewActionDesignProperties that describes the common view action parameters.

foo-action-design.types.ts
import { IViewActionDesignProperties } from '@helix/platform/view/api';

export interface IFooActionDesignProperties extends IViewActionDesignProperties {
  message: string;
}
  • foo-action-design-manager.service.ts

FooActionDesignManagerService is the service that is responsible for view action parameter validation at design time.

foo-action-design-manager.service.ts
import { Injectable } from '@angular/core';
import { IViewActionDesignManager } from '@helix/platform/view/api';
import { IViewComponentDesignValidationIssue } from '@helix/platform/view/designer';
import { Observable, of } from 'rxjs';
import { IFooActionDesignProperties } from './foo-action-design.types';

@Injectable()
export class FooActionDesignManagerService implements IViewActionDesignManager<IFooActionDesignProperties> {
 // This method will be called automatically to validate view action input parameters.
 validate(actionProperties: IFooActionDesignProperties, propertyName: string): Observable<IViewComponentDesignValidationIssue[]> {
   // Add custom validation here.
   return of([]);
  }
}
  • foo-action-design-model.class.ts

FooActionDesignModel class is responsible for design time behavior of the view action, including initialization, parameter configuration, and expression builder data dictionary updates.

Refer to the comments in the code below for more details.

foo-action-design-model.class.ts
import { Injector } from '@angular/core';
import { ExpressionFormControlComponent, IExpressionFormControlOptions } from '@helix/platform/shared/components';
import {
  IViewActionDesignPropertyEditorConfig,
  IViewActionDesignSandbox,
  IViewActionOutputDataDictionary,
  ViewActionDesignEditableProperties
} from '@helix/platform/view/api';
import { RxViewDesignerActionModel } from '@helix/platform/view/designer';
import { IFooActionDesignProperties } from './foo-action-design.types';

export class FooActionDesignModel extends RxViewDesignerActionModel {
 // This method is called when a new, or an existing view action is initialized in the view designer.
 // It returns values for all input parameters of the view action.
 static getInitialProperties(currentInputParams: ViewActionDesignEditableProperties<IFooActionDesignProperties>) {
   return {
     // initial values for custom input parameters
     message: '',
     // input parameter values of an existing view action that are already saved in the view
     ...currentInputParams
    };
  }

  constructor(protected injector: Injector,
              readonly sandbox: IViewActionDesignSandbox<IFooActionDesignProperties>) {
   super(injector, sandbox);

   // Configure view action input parameter editor.
   this.sandbox.setActionPropertyEditorConfig(this.getActionEditorConfig());

   // Add view action output parameters to the expression builder data dictionary.
   this.sandbox.setActionOutputDataDictionary(this.getActionOutputDataDictionary());
  }

 private getActionEditorConfig(): IViewActionDesignPropertyEditorConfig {
   return [
      {
        name: 'message',
        component: ExpressionFormControlComponent,
        options: {
          label: 'Message',
          isRequired: true,
          dataDictionary$: this.expressionConfigurator.getDataDictionary(),
          operators: this.expressionConfigurator.getOperators()
        } as IExpressionFormControlOptions
      }
    ];
  }

 private getActionOutputDataDictionary(): IViewActionOutputDataDictionary {
   // Add view action output parameters here.
   return [];
  }
}

 

Tip: For faster searching, add an asterisk to the end of your partial query. Example: cert*