-
Notifications
You must be signed in to change notification settings - Fork 474
Custom Components with Angular Elements
IMPORTANT NOTE: This feature is only available in the 5.x version of the Angular renderer and below. As of version 6.0, this feature will be officially deprecated. If you wish to still use this concept, you will need to copy the source code provided @ https://github.com/formio/angular/tree/5.5.x/projects/angular-formio/src/custom-component into your own application where it can be integrated within your own application.
NOTE: This is a community contributed feature to this module. Form.io company does not provide support for this feature.
If you have any questions, create a new issue in this repo by choosing "Custom Components Support Request".
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.
Live: https://angular-formio-custom-demo.netlify.com/
The following setup is tested with desktop Angular app created via Angular CLI. If you use different environment (e.g. Ionic) you may need to setup things differently.
@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';
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.
You should register your Angular component in the entryComponents
array. This won't be required after Angular v9 with Ivy.
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
// fieldOptions: ['values'], // Optional: explicit field options to get as `Input` from the schema (may edited by the editForm)
};
export function registerRatingComponent(injector: Injector) {
registerCustomFormioComponent(COMPONENT_OPTIONS, RatingWrapperComponent, injector);
}
Call the registration in the constructor of your NgModule like this:
export class AppModule {
constructor(injector: Injector) {
registerRatingComponent(injector);
}
}
You may want to customize your custom field via the editForm
. You can reach the options defined there via @Input()
as the following:
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
).
Due to performance reasons not all the options are bound to the component. If you want to reach fields from the schema, define in the fieldOptions
array at the field registration.
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 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.
As the Angular Lifecycle Hooks (e.g. ngOnInit()
) take care of the internal rendering state, you can't expect all inputs being populated with the proper value during the init hooks as the Elements are existing inside an "external" library - inside formio which may assign the options including the value asynchronously.
If you need to process the value of the input, put the logic inside a setter. E.g. if you want to coerce the input value as number:
import { coerceNumberProperty } from '@angular/cdk/coercion';
private _value: number;
@Input()
public set value(v: number | string) {
this._value = coerceNumberProperty(v, undefined);
}
public get value(): number | string {
return this._value;
}
If you don't want to register an Angular Component as you want to implement the logic in a simple JavaScript-based 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.
Example of registering a component with custom class:
import { Components } from 'angular-formio';
Components.setComponent('separator', SeparatorComponent);
Inside the Custom Angular Component, implement as the following:
@Output()
formioEvent = new EventEmitter<FormioEvent>();
And emit an event like:
this.formioEvent.emit({ eventName: 'submitButton', data: { state: 'submitted' } });
See details: https://github.com/formio/angular-formio/pull/443
See details: https://github.com/formio/angular-formio/issues/442#issuecomment-574263404
This feature is contributed by Merobal with support of CodingSans.
If you have any questions, create a new issue in this repo by choosing "Custom Components Support Request".