Skip to content

Custom Components with Angular Elements

Bálint edited this page Nov 7, 2019 · 9 revisions

Starting from angular-formio v4.3.0 you can register an Angular component as a formio field. It uses the @angular/elements in the background, so that package is required as peer dependency.

Setup

Polyfills

@webcomponents/custom-elements package is required.

After installing it via npm or yarn, add the following line to the angular.json file to the scripts array:

"node_modules/@webcomponents/custom-elements/src/native-shim.js"

And the following line to the app's polyfills.ts file:

import '@webcomponents/custom-elements/custom-elements.min';

Your Component

You should implement the FormioCustomComponent interface and define the required variables. The Component class may look like the following:

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormioCustomComponent } from 'angular-formio';

@Component({
  selector: 'app-rating-wrapper',
  templateUrl: './rating-wrapper.component.html',
  styleUrls: ['./rating-wrapper.component.scss']
})
export class RatingWrapperComponent implements FormioCustomComponent<number> {
  @Input()
  value: number;

  @Output()
  valueChange = new EventEmitter<number>();

  @Input()
  disabled: boolean;
}

The value input stores the value of the field. The valueChange output should be called upon value update, but please note that's only a change triggering event, the formio framework reads the value from the value field. So a value update may look like the following:

updateValue(payload: number) {
  this.value = payload; // Should be updated first
  this.valueChange.emit(payload); // Should be called after this.value update
}

The change event (output) is named as valueChange to keep compatibility with the Angular principles if you want to use the same component at other places inside your app.

NgModule entryComponents

You should register your Angular component in the entryComponents array. This won't be required after Angular v9 with Ivy.

Registration definition

Define the options

Add a new file next to your component. E.g. if you have rating-wrapper.component.ts place a new file next to it, e.g. rating-wrapper.formio.ts

Add the following content:

import { Injector } from '@angular/core';
import { FormioCustomComponentInfo, registerCustomFormioComponent } from 'angular-formio';
import { RatingWrapperComponent } from './rating-wrapper.component';

const COMPONENT_OPTIONS: FormioCustomComponentInfo = {
  type: 'myrating', // custom type. Formio will identify the field with this type.
  selector: 'my-rating', // custom selector. Angular Elements will create a custom html tag with this selector
  title: 'Rating', // Title of the component
  group: 'basic', // Build Group
  icon: 'fa fa-star', // Icon
//  template: 'input', // Optional: define a template for the element. Default: input
//  changeEvent: 'valueChange', // Optional: define the changeEvent when the formio updates the value in the state. Default: 'valueChange',
//  editForm: Components.components.textfield.editForm, // Optional: define the editForm of the field. Default: the editForm of a textfield
//  documentation: '', // Optional: define the documentation of the field
//  weight: 0, // Optional: define the weight in the builder group
//  schema: {}, // Optional: define extra default schema for the field
//  extraValidators: [], // Optional: define extra validators  for the field
//  emptyValue: null, // Optional: the emptyValue of the field
};

export function registerRatingComponent(injector: Injector) {
  registerCustomFormioComponent(COMPONENT_OPTIONS, RatingWrapperComponent, injector);
}

Register in your AppModule

Call the registration in the constructor of your NgModule like this:

export class AppModule {
  constructor(injector: Injector) {
    registerRatingComponent(injector);
  }
}

Options

You may want to customize your custom field via the editForm. You can reach the options defined there via @Input() as the following:

Default Options

The standard options defined for inputs (e.g. the placeholder) are bound as attributes so you can reach those in the component thanks to Angular Elements (e.g. @Input() placeholder: string).

Custom Options

Due to performance reasons not all the options are bound to the component. If you want to define a custom option in the editForm, you can do as the following:

{ key: 'customOptions.myOption', [rest of the field definition] }

And the customOptions defined there will be bound flattened, e.g. @Input() myOption: string.

For Custom Options you may need to create your own editForm (from scratch or extend an existing one) and define in the COMPONENT_OPTIONS described above. You can define fields there following the default schema. :Please remember to put customOptions in the key of the fields.:

E.g.

export function minimalEditForm() {
  return {
    components: [
      { key: 'type', type: 'hidden' },
      {
        weight: 0,
        type: 'textfield',
        input: true,
        key: 'label',
        label: 'Label',
        placeholder: 'Label',
        validate: {
          required: true,
        },
      },
      {
        weight: 10,
        type: 'textfield',
        input: true,
        key: 'key',
        label: 'Field Code',
        placeholder: 'Field Code',
        tooltip: 'The code/key/ID/name of the field.',
        validate: {
          required: true,
          maxLength: 128,
          pattern: '[A-Za-z]\\w*',
          patternMessage:
            'The property name must only contain alphanumeric characters, underscores and should only be started by any letter character.',
        },
      },
      {
        weight: 20,
        type: 'textfield',
        input: true,
        key: 'customOptions.myOption',
        label: 'My Custom Option',
        placeholder: 'My Custom Option',
        validate: {
          required: true,
        },
      },
    ],
  };
}

and

const COMPONENT_OPTIONS: FormioCustomComponentInfo = {
  [...]
  editForm: minimalEditForm,
  [...]
};

Validations

Validations are bound to the component as well, similar to the Custom Options. E.g. if you define validate.required and validate.min you can reach those as @Input() required: boolean; and @Input() min: number;.

Please note that if you define more validations on top of the default ones, and you want formio to process those, you need to define those in the COMPONENT_OPTIONS like: extraValidators: ['min'] as well.

Register a single class as Custom Component

If you don't want to register an Angular Component as you want to implement the logic in a simple class, take a look at the CheckMatrix example: https://github.com/formio/angular-demo/blob/master/src/app/components/CheckMatrix.js

Please remember that the Formio.registerComponent method is not available in TypeScript environment due to typings limitations, call the Components.addComponent method instead.

Clone this wiki locally