@rxap/forms
Version:
This package provides a set of tools and directives to simplify working with Angular forms, including reactive forms, custom validators, and form directives for handling loading, submitting, and error states. It offers decorators for defining forms and co
1,178 lines (995 loc) • 41.6 kB
HTML
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>angular-forms</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="../images/favicon.ico">
<link rel="stylesheet" href="../styles/style.css">
<link rel="stylesheet" href="../styles/dark.css">
</head>
<body>
<script>
// Blocking script to avoid flickering dark mode
// Dark mode toggle button
var useDark = window.matchMedia('(prefers-color-scheme: dark)');
var darkModeState = useDark.matches;
var $darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input');
var $darkModeToggles = document.querySelectorAll('.dark-mode-switch');
var darkModeStateLocal = localStorage.getItem('compodoc_darkmode-state');
function checkToggle(check) {
for (var i = 0; i < $darkModeToggleSwitchers.length; i++) {
$darkModeToggleSwitchers[i].checked = check;
}
}
function toggleDarkMode(state) {
if (window.localStorage) {
localStorage.setItem('compodoc_darkmode-state', state);
}
checkToggle(state);
const hasClass = document.body.classList.contains('dark');
if (state) {
for (var i = 0; i < $darkModeToggles.length; i++) {
$darkModeToggles[i].classList.add('dark');
}
if (!hasClass) {
document.body.classList.add('dark');
}
} else {
for (var i = 0; i < $darkModeToggles.length; i++) {
$darkModeToggles[i].classList.remove('dark');
}
if (hasClass) {
document.body.classList.remove('dark');
}
}
}
useDark.addEventListener('change', function (evt) {
toggleDarkMode(evt.matches);
});
if (darkModeStateLocal) {
darkModeState = darkModeStateLocal === 'true';
}
toggleDarkMode(darkModeState);
</script>
<div class="navbar navbar-default navbar-fixed-top d-md-none p-0">
<div class="d-flex">
<a href="../" class="navbar-brand">angular-forms</a>
<button type="button" class="btn btn-default btn-menu ion-ios-menu" id="btn-menu"></button>
</div>
</div>
<div class="xs-menu menu" id="mobile-menu">
<div id="book-search-input" role="search"><input type="text" placeholder="Type to search"></div> <compodoc-menu></compodoc-menu>
</div>
<div class="container-fluid main">
<div class="row main">
<div class="d-none d-md-block menu">
<compodoc-menu mode="normal"></compodoc-menu>
</div>
<!-- START CONTENT -->
<div class="content class">
<div class="content-data">
<ol class="breadcrumb">
<li class="breadcrumb-item">Classes</li>
<li class="breadcrumb-item" >FormArrayControlManager</li>
</ol>
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a href="#info"
class="nav-link"
class="nav-link active"
role="tab" id="info-tab" data-bs-toggle="tab" data-link="info">Info</a>
</li>
<li class="nav-item">
<a href="#source"
class="nav-link"
role="tab" id="source-tab" data-bs-toggle="tab" data-link="source">Source</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade active in" id="info">
<p class="comment">
<h3>File</h3>
</p>
<p class="comment">
<code>src/lib/form-builder.ts</code>
</p>
<section data-compodoc="block-index">
<h3 id="index">Index</h3>
<table class="table table-sm table-bordered index-table">
<tbody>
<tr>
<td class="col-md-4">
<h6><b>Methods</b></h6>
</td>
</tr>
<tr>
<td class="col-md-4">
<ul class="index-list">
<li>
<span class="modifier">Public</span>
<a href="#inserted" >inserted</a>
</li>
<li>
<span class="modifier">Public</span>
<a href="#removed" >removed</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</section>
<section data-compodoc="block-constructor">
<h3 id="constructor">Constructor</h3>
<table class="table table-sm table-bordered">
<tbody>
<tr>
<td class="col-md-4">
<code>constructor(form: T, controlId)</code>
</td>
</tr>
<tr>
<td class="col-md-4">
<div class="io-line">Defined in <a href="" data-line="37" class="link-to-prism">src/lib/form-builder.ts:37</a></div>
</td>
</tr>
<tr>
<td class="col-md-4">
<div>
<b>Parameters :</b>
<table class="params">
<thead>
<tr>
<td>Name</td>
<td>Type</td>
<td>Optional</td>
</tr>
</thead>
<tbody>
<tr>
<td>form</td>
<td>
<code>T</code>
</td>
<td>
No
</td>
</tr>
<tr>
<td>controlId</td>
<td>
</td>
<td>
No
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</section>
<section data-compodoc="block-methods">
<h3 id="methods">
Methods
</h3>
<table class="table table-sm table-bordered">
<tbody>
<tr>
<td class="col-md-4">
<a name="inserted"></a>
<span class="name">
<span class="modifier">Public</span>
<span ><b>inserted</b></span>
<a href="#inserted"><span class="icon ion-ios-link"></span></a>
</span>
</td>
</tr>
<tr>
<td class="col-md-4">
<span class="modifier-icon icon ion-ios-reset"></span>
<code>inserted(index: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/number" target="_blank">number</a>, controlOrDefinition: AbstractControl | FormDefinition)</code>
</td>
</tr>
<tr>
<td class="col-md-4">
<div class="io-line">Defined in <a href="" data-line="41"
class="link-to-prism">src/lib/form-builder.ts:41</a></div>
</td>
</tr>
<tr>
<td class="col-md-4">
<div class="io-description">
<b>Parameters :</b>
<table class="params">
<thead>
<tr>
<td>Name</td>
<td>Type</td>
<td>Optional</td>
</tr>
</thead>
<tbody>
<tr>
<td>index</td>
<td>
<code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/number" target="_blank" >number</a></code>
</td>
<td>
No
</td>
</tr>
<tr>
<td>controlOrDefinition</td>
<td>
<code>AbstractControl | FormDefinition</code>
</td>
<td>
No
</td>
</tr>
</tbody>
</table>
</div>
<div class="io-description">
<b>Returns : </b> <code><a href="https://www.typescriptlang.org/docs/handbook/basic-types.html" target="_blank" >void</a></code>
</div>
<div class="io-description">
</div>
</td>
</tr>
</tbody>
</table>
<table class="table table-sm table-bordered">
<tbody>
<tr>
<td class="col-md-4">
<a name="removed"></a>
<span class="name">
<span class="modifier">Public</span>
<span ><b>removed</b></span>
<a href="#removed"><span class="icon ion-ios-link"></span></a>
</span>
</td>
</tr>
<tr>
<td class="col-md-4">
<span class="modifier-icon icon ion-ios-reset"></span>
<code>removed(index: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/number" target="_blank">number</a>)</code>
</td>
</tr>
<tr>
<td class="col-md-4">
<div class="io-line">Defined in <a href="" data-line="55"
class="link-to-prism">src/lib/form-builder.ts:55</a></div>
</td>
</tr>
<tr>
<td class="col-md-4">
<div class="io-description">
<b>Parameters :</b>
<table class="params">
<thead>
<tr>
<td>Name</td>
<td>Type</td>
<td>Optional</td>
</tr>
</thead>
<tbody>
<tr>
<td>index</td>
<td>
<code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/number" target="_blank" >number</a></code>
</td>
<td>
No
</td>
</tr>
</tbody>
</table>
</div>
<div class="io-description">
<b>Returns : </b> <code><a href="https://www.typescriptlang.org/docs/handbook/basic-types.html" target="_blank" >void</a></code>
</div>
<div class="io-description">
</div>
</td>
</tr>
</tbody>
</table>
</section>
</div>
<div class="tab-pane fade tab-source-code" id="source">
<pre class="line-numbers compodoc-sourcecode"><code class="language-typescript">import {
Injector,
isDevMode,
StaticProvider,
} from '@angular/core';
import {
assertIsArray,
assertIsFunction,
assertIsObject,
Constructor,
HasRxapOnInitMethod,
} from '@rxap/utilities';
import { MetadataKeys } from './decorators/metadata-keys';
import {
AbstractControl,
AsyncValidatorFn,
ValidatorFn,
Validators,
} from '@angular/forms';
import {
ChangeFn,
FormDefinition,
FormDefinitionMetadata,
FormType,
RxapAbstractControlOptions,
RxapAbstractControlOptionsWithDefinition,
SetValueFn,
} from './model';
import { RxapFormArray } from './form-array';
import { RxapFormGroup } from './form-group';
import { RxapFormControl } from './form-control';
import { GetDefinitionMetadata } from '@rxap/definition';
import { ControlSetValueOptions } from './decorators/control-set-value';
import { getMetadata } from '@rxap/reflect-metadata';
export class FormArrayControlManager<T extends FormDefinition> {
constructor(private readonly form: T, private readonly controlId: keyof T) {
}
public inserted(
index: number,
controlOrDefinition: AbstractControl | FormDefinition,
): void {
const controlContainer = this.form[this.controlId];
if (Array.isArray(controlContainer)) {
controlContainer.splice(index, 0, controlOrDefinition);
} else {
throw new Error(
'Could not register inserted control or definition. The control id does not points to an array of control or definition instance!',
);
}
}
public removed(index: number): void {
const controlContainer = this.form[this.controlId];
if (Array.isArray(controlContainer)) {
controlContainer.splice(index, 1);
} else {
throw new Error(
'Could not register inserted control or definition. The control id does not points to an array of control or definition instance!',
);
}
}
}
export class RxapFormBuilder<
Data extends Record<string, any> = any,
Form extends FormType<Data> = FormType<Data>
> {
private readonly formArrayGroups: Map<
string,
RxapAbstractControlOptionsWithDefinition
>;
private readonly formArrayControls: Map<string, RxapAbstractControlOptions>;
private readonly formControls: Map<string, RxapAbstractControlOptions>;
private readonly formGroups: Map<
string,
RxapAbstractControlOptionsWithDefinition
>;
private readonly formOptions: FormDefinitionMetadata;
private readonly validators: Map<string, Set<string>>;
private readonly asyncValidators: Map<string, Set<string>>;
private readonly controlChanges: Map<string, Set<string>>;
private readonly controlSetValue: Map<string, Set<ControlSetValueOptions>>;
private readonly providers: StaticProvider[];
constructor(
private readonly definition: Constructor<Form>,
private readonly injector: Injector = Injector.NULL,
providers: StaticProvider[] = [],
) {
this.formArrayGroups = this.extractArrayGroups();
this.formArrayControls = this.extractArrayControls();
this.formControls = this.extractControls();
this.formGroups = this.extractGroups();
this.validators = this.extractValidators();
this.asyncValidators = this.extractAsyncValidators();
this.controlChanges = this.extractControlChanges();
this.controlSetValue = this.extractControlSetValue();
const formOptions = (this.formOptions = this.extractFormOptions());
// merge the providers from the from options with the providers from the
// constructor. If the provider is defined in the form options and constructor
// then the provider from the constructor will be used.
this.providers = [ ...providers, ...(formOptions.providers ?? []) ].filter(
(provider: any, index, self) =>
provider['provide'] &&
self.findIndex(
(selfProvider: any) =>
selfProvider.provide &&
selfProvider.provide === provider.provide,
) === index,
);
}
public build<T extends (FormDefinition<Data> | FormType<Data>)>(
state: Readonly<any> = {},
options: Partial<FormDefinitionMetadata & { controlId?: string }> = {},
): T {
const injector = Injector.create({
name: `rxap/form-builder/${ this.formOptions.id }`,
parent: this.injector,
providers: this.providers,
});
let form: Record<string, Function> & FormDefinition;
// don't use the notFoundValue feature of the injector.
// if used for each call of the get method an "empty" or "fallback"
// instance is created
try {
form = injector.get(this.definition);
} catch (e: any) {
if (e.name === 'NullInjectorError') {
if (e.message.includes(`No provider for ${ this.definition.name }`)) {
if (isDevMode()) {
console.warn(
`Could not inject the definition instance. Fallback and call the constructor of the definition class: ${ e.message }`);
}
form = new this.definition() as any;
} else {
throw e;
}
} else {
throw new Error(`Could not inject the definition instance: ${ e.message }`);
}
}
const controls: Record<string, AbstractControl> = {};
this.buildControls(state, form, controls);
this.buildGroups(state, form, controls);
this.buildArrayGroups(state, form, controls);
this.buildArrayControls(state, form, controls);
const formOptions = {
...this.formOptions,
controlId:
options.controlId ?? (options.id ? options.id : this.formOptions.id),
...options,
};
form.rxapFormGroup = new RxapFormGroup(controls, formOptions);
Reflect.set(form.rxapFormGroup, '_rxapFormDefinition', form);
form.rxapMetadata = this.formOptions;
if (HasRxapOnInitMethod(form)) {
form.rxapOnInit();
}
for (const control of Object.values(form.rxapFormGroup.controls)) {
if (control.controlId) {
const controlSetValueOnInit = Array.from(
this.controlSetValue.get(control.controlId) ?? [],
).filter((controlSetValueOptions) => controlSetValueOptions.initial);
for (const onSetValue of this.coerceToFnArray<SetValueFn>(
form,
controlSetValueOnInit,
)) {
onSetValue(control.value, { initial: true });
}
}
}
return form as any;
}
private buildArrayControls(
builderFormState: any,
form: FormDefinition & Record<string, Function>,
controls: Record<string, AbstractControl>,
): void {
for (const [ controlId, options ] of this.formArrayControls.entries()) {
const formState = this.coerceToArrayFormState(
controlId,
[],
builderFormState,
options.state,
);
assertIsArray(formState);
const formControls: any = formState.map(
(fs, index) => new RxapFormControl(fs, { controlId: index.toFixed(0) }),
);
const manager = new FormArrayControlManager(form, controlId);
const formArray =
(formControls.rxapFormArray =
controls[controlId] =
new RxapFormArray(
// TODO : add the formControlOptions property. To definition the form control options
formControls,
{
...options,
controlId,
validators: [],
asyncValidators: [],
controlInsertedFn: manager.inserted.bind(manager),
controlRemovedFn: manager.removed.bind(manager),
builder: (state, controlOptions) =>
new RxapFormControl(state, controlOptions),
},
));
// TODO : add support for injectable validators
formArray.setValidators(
this.coerceToValidatorArray(
form,
options.validators,
this.validators.get(controlId),
),
false,
);
formArray.setAsyncValidators(
this.coerceToValidatorArray(
form,
options.asyncValidators,
this.asyncValidators.get(controlId),
),
false,
);
Reflect.set(form, controlId, formControls);
}
}
private buildArrayGroups(
builderFormState: any,
form: FormDefinition & Record<string, Function>,
controls: Record<string, AbstractControl>,
): void {
for (const [ controlId, options ] of this.formArrayGroups.entries()) {
const formState = this.coerceToArrayFormState(
controlId,
[],
builderFormState,
options.state,
);
assertIsArray(formState);
const formGroupBuilder = new RxapFormBuilder(
options.definition,
this.injector,
this.providers,
);
// TODO : add the formGroupOptions property. To definition of the form group options
const formGroupDefinitions: any = formState.map((fs, index) =>
formGroupBuilder.build(fs, { id: index.toFixed(0) }),
);
const manager = new FormArrayControlManager(form, controlId);
const formArray =
(formGroupDefinitions.rxapFormArray =
controls[controlId] =
new RxapFormArray(
formGroupDefinitions.map(
(fgd: FormDefinition) => fgd.rxapFormGroup,
),
{
...options,
builder: formGroupBuilder.build.bind(formGroupBuilder) as any,
controlId,
validators: [],
asyncValidators: [],
controlInsertedFn: manager.inserted.bind(manager),
controlRemovedFn: manager.removed.bind(manager),
},
));
// TODO : add support for injectable validators
formArray.setValidators(
this.coerceToValidatorArray(
form,
options.validators,
this.validators.get(controlId),
),
false,
);
formArray.setAsyncValidators(
this.coerceToValidatorArray(
form,
options.asyncValidators,
this.asyncValidators.get(controlId),
),
false,
);
Reflect.set(form, controlId, formGroupDefinitions);
}
}
private buildGroups(
builderFormState: any,
form: Record<string, Function>,
controls: Record<string, AbstractControl>,
): void {
for (const [ controlId, options ] of this.formGroups.entries()) {
const formState = this.coerceToGroupFormState(
controlId,
{},
builderFormState,
options.state,
);
assertIsObject(formState);
const formGroupDefinition: FormDefinition = new RxapFormBuilder(
options.definition,
this.injector,
this.providers,
).build(formState, {
...options,
validators: [],
asyncValidators: [],
});
controls[controlId] = formGroupDefinition.rxapFormGroup!;
const formGroup = formGroupDefinition.rxapFormGroup;
// TODO : add support for injectable validators
formGroup.setValidators(
this.coerceToValidatorArray(
form,
options.validators,
this.validators.get(controlId),
),
false,
);
formGroup.setAsyncValidators(
this.coerceToValidatorArray(
form,
options.asyncValidators,
this.asyncValidators.get(controlId),
),
false,
);
// set the form group definition instance to the form definition instance
Reflect.set(form, controlId, formGroupDefinition);
}
}
private buildControls(
builderFormState: any,
form: Record<string, Function>,
controls: Record<string, AbstractControl>,
): void {
for (const [ controlId, options ] of this.formControls.entries()) {
const control = (controls[controlId] = new (options.controlType ??
RxapFormControl)(
this.coerceToControlFormState(
controlId,
null,
builderFormState,
options.state,
),
{
...options,
controlId,
validators: [],
asyncValidators: [],
},
));
const injectValidators: ValidatorFn[] = [];
const injectAsyncValidators: AsyncValidatorFn[] = [];
for (const injectValidator of options.injectValidators ?? []) {
const injectableValidator = this.injector.get(injectValidator);
if (injectableValidator.validate) {
injectValidators.push(
injectableValidator.validate.bind(injectableValidator),
);
}
if (injectableValidator.asyncValidate) {
injectAsyncValidators.push(
injectableValidator.asyncValidate.bind(injectableValidator),
);
}
}
control.setValidators(
this.coerceToValidatorArray(
form,
options.validators,
this.validators.get(controlId),
injectValidators,
),
false,
);
control.setAsyncValidators(
this.coerceToValidatorArray(
form,
options.asyncValidators,
this.asyncValidators.get(controlId),
injectAsyncValidators,
),
false,
);
// register all control on change function with the form control
this.coerceToFnArray<ChangeFn>(
form,
this.controlChanges.get(controlId),
).forEach((changeFn) => control.registerOnChange(changeFn));
// register all control on set value function with the form control
this.coerceToFnArray<SetValueFn>(
form,
this.controlSetValue.get(controlId),
).forEach((setValueFn) => control.registerOnSetValue(setValueFn));
if (Array.isArray(options.validators)) {
if (
options.validators.includes(Validators.required) ||
options.validators.includes(Validators.requiredTrue)
) {
Reflect.set(control, 'hasRequiredValidator', true);
}
}
// set the form control instance to the form definition instance
Reflect.set(form, controlId, control);
}
}
/**
* Coerce to a function array.
*
* @param form the current form definition instance
* @param methodKeys A set of propertyKeys that points to form
* definition instance methods
*/
private coerceToFnArray<T extends Function>(
form: Record<string, Function>,
methodKeys?: Iterable<string | { propertyKey: string }>,
): Array<T> {
const changes: Array<T> = [];
if (methodKeys) {
for (const propertyKeyOrOptions of methodKeys) {
const propertyKey =
typeof propertyKeyOrOptions === 'string'
? propertyKeyOrOptions
: propertyKeyOrOptions.propertyKey;
assertIsFunction(form[propertyKey]);
changes.push((form[propertyKey] as T).bind(form));
}
}
return changes;
}
/**
* Coerce to a validator function array.
*
* @param form the current form definition instance
* @param optionsValidators The options validator functions
* @param validatorMethodKeys A set of propertyKeys that points to form
* definition instance methods
* @param injectValidators Injected validator functions
*/
private coerceToValidatorArray<VF extends ValidatorFn | AsyncValidatorFn>(
form: Record<string, Function>,
optionsValidators?: Array<VF> | VF | null,
validatorMethodKeys?: Set<string>,
injectValidators?: Array<VF>,
): Array<VF> {
const validators: Array<VF> = (
optionsValidators ? Array.isArray(optionsValidators) ? optionsValidators : [ optionsValidators ] : []
).slice();
if (validatorMethodKeys) {
validatorMethodKeys.forEach((propertyKey) => {
assertIsFunction(form[propertyKey]);
validators.push(form[propertyKey].bind(form) as VF);
});
}
if (injectValidators) {
validators.push(...injectValidators);
}
return validators;
}
/**
* Coerce to the form state for the specified controlId. If none form state is
* found the default form state will be returned.
*
* @param controlId A control id
* @param defaultFormState The default form state
* @param builderFormState The form state provides as build state parameter
* @param optionsFormState The form state set by the decorator in the form definition
*/
private coerceToControlFormState(
controlId: string,
defaultFormState: any,
builderFormState: Record<string, any>,
optionsFormState?: any,
): any {
let formState = defaultFormState;
if (builderFormState && builderFormState[controlId] !== undefined) {
formState = builderFormState[controlId];
} else if (optionsFormState !== undefined) {
formState = optionsFormState ?? null;
}
if (typeof formState === 'function') {
formState = formState();
}
return formState;
}
/**
* Coerce to the form state for the specified controlId. If none form state is
* found the default form state will be returned.
*
* The difference to the method coerceToControlFormState is that null will be
* handled as undefined and the defaultFormState is used
*
* @param controlId A control id
* @param defaultFormState The default form state
* @param builderFormState The form state provides as build state parameter
* @param optionsFormState The form state set by the decorator in the form definition
*/
private coerceToGroupFormState(
controlId: string,
defaultFormState: any,
builderFormState: Record<string, any>,
optionsFormState?: any,
): any {
let formState = defaultFormState;
if (builderFormState && builderFormState[controlId]) {
formState = builderFormState[controlId];
} else if (optionsFormState) {
formState = optionsFormState;
}
if (typeof formState === 'function') {
formState = formState();
if (!formState) {
throw new Error(`The form state function for the form group '${ controlId }' returns a empty value (null/undefined)`);
}
if (typeof formState !== 'object') {
throw new Error(`The form state function for the form group '${ controlId }' returns a non object like value instead '${ typeof formState }'`);
}
}
return formState;
}
/**
* Coerce to the form state for the specified controlId. If none form state is
* found the default form state will be returned.
*
* The difference to the method coerceToControlFormState is that null will be
* handled as undefined and the defaultFormState is used
*
* @param controlId A control id
* @param defaultFormState The default form state
* @param builderFormState The form state provides as build state parameter
* @param optionsFormState The form state set by the decorator in the form definition
*/
private coerceToArrayFormState(
controlId: string,
defaultFormState: any[] | (() => any[]),
builderFormState: Record<string, any>,
optionsFormState?: any,
): any[] {
let formState = defaultFormState;
if (builderFormState && builderFormState[controlId] && Array.isArray(builderFormState[controlId])) {
formState = builderFormState[controlId];
} else if (optionsFormState && Array.isArray(optionsFormState)) {
formState = optionsFormState;
}
if (typeof formState === 'function') {
formState = formState();
if (!formState) {
throw new Error(`The form state function for the form array '${ controlId }' returns a empty value (null/undefined)`);
}
if (!Array.isArray(formState)) {
throw new Error(`The form state function for the form array '${ controlId }' returns a non array like value instead '${ typeof formState }'`);
}
}
return formState;
}
private extractControlChanges(): Map<string, Set<string>> {
return (
getMetadata(MetadataKeys.CONTROL_CHANGES, this.definition.prototype) ??
new Map<string, Set<string>>()
);
}
private extractControlSetValue(): Map<string, Set<ControlSetValueOptions>> {
return (
getMetadata(MetadataKeys.CONTROL_SET_VALUE, this.definition.prototype) ??
new Map<string, Set<ControlSetValueOptions>>()
);
}
private extractValidators(): Map<string, Set<string>> {
return (
getMetadata(MetadataKeys.CONTROL_VALIDATORS, this.definition.prototype) ??
new Map<string, Set<string>>()
);
}
private extractAsyncValidators(): Map<string, Set<string>> {
return (
getMetadata(
MetadataKeys.CONTROL_ASYNC_VALIDATORS,
this.definition.prototype,
) ?? new Map<string, Set<string>>()
);
}
private extractArrayGroups(): Map<
string,
RxapAbstractControlOptionsWithDefinition
> {
return (
getMetadata(MetadataKeys.FORM_ARRAY_GROUPS, this.definition.prototype) ??
new Map<string, RxapAbstractControlOptionsWithDefinition>()
);
}
private extractArrayControls(): Map<string, RxapAbstractControlOptions> {
return (
getMetadata(
MetadataKeys.FORM_ARRAY_CONTROLS,
this.definition.prototype,
) ?? new Map<string, RxapAbstractControlOptions>()
);
}
private extractGroups(): Map<
string,
RxapAbstractControlOptionsWithDefinition
> {
return (
getMetadata(MetadataKeys.FORM_GROUPS, this.definition.prototype) ??
new Map<string, RxapAbstractControlOptionsWithDefinition>()
);
}
private extractControls(): Map<string, RxapAbstractControlOptions> {
return (
getMetadata(MetadataKeys.FORM_CONTROLS, this.definition.prototype) ??
new Map<string, RxapAbstractControlOptions>()
);
}
private extractFormOptions(): FormDefinitionMetadata {
const options = GetDefinitionMetadata(this.definition);
if (!options || !options['id']) {
throw new Error('Ensure that the @UseForm decorator is used.');
}
if (!options['providers']) {
options['providers'] = [];
}
return options;
}
}
</code></pre>
</div>
</div>
</div><div class="search-results">
<div class="has-results">
<h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
<ul class="search-results-list"></ul>
</div>
<div class="no-results">
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
</div>
</div>
</div>
<!-- END CONTENT -->
</div>
</div>
<label class="dark-mode-switch">
<input type="checkbox">
<span class="slider">
<svg class="slider-icon" viewBox="0 0 24 24" fill="none" height="20" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" width="20" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path>
</svg>
</span>
</label>
<script>
var COMPODOC_CURRENT_PAGE_DEPTH = 1;
var COMPODOC_CURRENT_PAGE_CONTEXT = 'class';
var COMPODOC_CURRENT_PAGE_URL = 'FormArrayControlManager.html';
var MAX_SEARCH_RESULTS = 15;
</script>
<script>
$darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input');
checkToggle(darkModeState);
if ($darkModeToggleSwitchers.length > 0) {
for (var i = 0; i < $darkModeToggleSwitchers.length; i++) {
$darkModeToggleSwitchers[i].addEventListener('change', function (event) {
darkModeState = !darkModeState;
toggleDarkMode(darkModeState);
});
}
}
</script>
<script src="../js/libs/custom-elements.min.js"></script>
<script src="../js/libs/lit-html.js"></script>
<script src="../js/menu-wc.js" defer></script>
<script nomodule src="../js/menu-wc_es5.js" defer></script>
<script src="../js/libs/bootstrap-native.js"></script>
<script src="../js/libs/es6-shim.min.js"></script>
<script src="../js/libs/EventDispatcher.js"></script>
<script src="../js/libs/promise.min.js"></script>
<script src="../js/libs/zepto.min.js"></script>
<script src="../js/compodoc.js"></script>
<script src="../js/tabs.js"></script>
<script src="../js/menu.js"></script>
<script src="../js/libs/clipboard.min.js"></script>
<script src="../js/libs/prism.js"></script>
<script src="../js/sourceCode.js"></script>
<script src="../js/search/search.js"></script>
<script src="../js/search/lunr.min.js"></script>
<script src="../js/search/search-lunr.js"></script>
<script src="../js/search/search_index.js"></script>
<script src="../js/lazy-load-graphs.js"></script>
</body>
</html>