@3kles/kles-material-dynamicforms
v22.6.1
Published
<!--[](http://gitlab.3kles.local/angular/klesmaterialdynamicforms/-/commits/master)-->
Keywords
Readme
@3kles/kles-material-dynamicforms
kles-material-dynamicforms is a component library to build Material Angular Form.
Changelog
Check out the changelog to check all the latest changes.
Models
Advanced CSS Grid layout
Add layout to an individual root field to enable CSS Grid automatically.
direction="grid" remains available to force it when no field declares a layout.
The defaults are a 12-column grid, no gap, colSpan: 12, and rowSpan: 1.
const fields: KlesFormElement[] = [
{ name: 'firstname', component: KlesFormInputComponent, layout: { colSpan: 6, responsive: { xs: { colSpan: 12 } } } },
{ name: 'lastname', component: KlesFormInputComponent, layout: { colSpan: 6, responsive: { xs: { colSpan: 12 } } } },
{ name: 'email', component: KlesFormInputComponent, layout: { colSpan: 12 } },
];Configure the root with [layout]="{ columns: 12, gap: '16px' }". Structural
section and layoutGroup elements can contain nested fields and establish
independent grids through layoutConfig; they do not create Angular form
controls. layoutGroup is deliberately distinct from the existing data-bearing
EnumType.group. The legacy row, column, wrap, and EnumType.group APIs
are unchanged.
Collapsible sections
Set collapsible: true on a section to render a Material expansion panel.
It starts open by default; use expanded: false to start closed. Sections
without collapsible keep their existing appearance. Panels open and close
independently, including nested sections. Collapsing keeps fields mounted,
so their values, validation, and subscriptions remain active.
const fields: KlesFormElement[] = [
{
type: 'section',
title: 'Additional information',
description: 'Optional details',
icon: 'info',
collapsible: true,
expanded: false,
fields: [{ name: 'notes', component: KlesFormInputComponent }],
},
];Use a ViewChild reference to open or close every collapsible section, including
nested sections, after the view is initialized:
@ViewChild(KlesDynamicFormComponent) dynamicForm!: KlesDynamicFormComponent;
openAll(): void {
this.dynamicForm.expandAllSections();
}
closeAll(): void {
this.dynamicForm.collapseAllSections();
}Updating field configurations
Assign a new fields array to synchronize the root form, including fields
inside nested dynamicFields, section and layoutGroup containers. Keep the same
configuration object for an unchanged field; replace the object when its
component configuration changes. Visual containers retain their identity when
the same container object is reused.
Compatible fields retain their control, current value, dirty/touched flags, and
UI state. Compatibility requires the same resolved component and control
factory, updateOn, and nonNullable. An incompatible field gets a new control
with its declared initial value. Changing value on a compatible field does
not overwrite user input; use the control's setValue or patchValue to do so.
Validator changes and changes to the configured disabled flag are applied to
existing controls. An unrelated update preserves programmatic enabling or
disabling. UI defaults that change are patched into the existing leaf state;
unrelated runtime UI adjustments are preserved. Unchanged mounted fields keep
their components, focus, option subscriptions, and valueChanges callbacks.
Their injected sibling list reflects additions and removals without a rebuild.
Removed fields lose their controls and UI states. Names must be unique across
all visual containers sharing the root form; duplicates are rejected before
changing controls. Structural updates emit the final form value after
synchronization, without intermediate structural values. Removed or replaced
controls are detached from their old parent. _onLoaded still emits after a
new fields input has been synchronized.
Recursive removal and schema updates inside data-bearing EnumType.group
and arrays are outside this stage. Their existing behavior is retained.
Dependent child fields: API contract
Dependency execution and automatic child synchronization are active.
dependsOn and resolveFields are optional properties on dynamicFields,
section and layoutGroup. The component observes dependencies, invokes the resolver and
exposes its results through the fieldResolutions Signal. Successful results
replace the container's effective children, synchronize controls and UI state,
and render at its position. The supplied fields configuration is never mutated.
Use dynamicFields for a transparent insertion slot. It creates no visual
wrapper, grid or Angular FormGroup. Its children become siblings of the surrounding
fields, preserving the parent's row, column or grid layout. Nested slots are
transparent too. Apply layout to individual children when grid placement is needed;
these placements enable the root grid just like placements on direct fields.
Static fields: [] supplies the initial children and may contain fallback fields.
Use section or layoutGroup when the returned fields need a titled section or
an independent grid. All three variants share the same dependency engine and
belong to the same form group as their static children.
The following configuration inserts the returned fields below the select:
import {
EnumType,
KlesFormElement,
} from '@3kles/kles-material-dynamicforms';
// dossierService.loadFields returns KlesFormElement[], a Promise, or an Observable.
const fields: KlesFormElement[] = [
{
name: 'typeDossier',
type: EnumType.select,
options: ['individual', 'company'],
},
{
type: 'dynamicFields',
fields: [],
dependsOn: ['typeDossier'],
resolveFields: ({ values }) => {
const dossierType = values['typeDossier'];
return typeof dossierType === 'string' && dossierType
? dossierService.loadFields(dossierType)
: [];
},
},
];The resolver receives an IKlesFormFieldsResolveEvent containing element
(the container), group (the root form), values (its getRawValue() snapshot,
including disabled controls), and context (the current external context or
null). Use the captured values for asynchronous work; group remains live.
Treat the snapshot and its nested values as read-only.
The dependency engine follows this execution contract:
- Dependencies: paths are relative to the root form, including inside nested
visual containers. Dot notation such as
address.countryfollows Angular control paths; wildcards and relative-path syntax are not supported. Empty or missing paths and dependency cycles must produce diagnostics instead of silently starting a resolver. Resolution waits until every declared control exists. Duplicate paths are observed once. Cycles are detected from static children and known resolved definitions; names that have never been returned cannot be inferred in advance. Unrelated valid branches continue to resolve when another branch has a diagnostic. - Triggers: resolve once after the form and declared dependencies are ready,
then whenever any dependency changes according to
Object.is. With an omitted or emptydependsOn, resolve only initially. Object values must be replaced to trigger resolution. Changingcontextalone is not a trigger in this first contract; existing Signal-basedresolveUibehavior is separate. - Results: accept
readonly KlesFormElement[], a Promise of that array, or an Observable of arrays. Each result replaces all container children; results are never appended. An empty array removes the children. Staticfieldsremains required and supplies the initial children. WithoutresolveFields,dependsOnhas no effect. - Concurrency and lifetime: the latest dependency values win. Unsubscribe from the previous Observable and ignore obsolete Promise results. Removing a container or replacing its resolver/dependencies disposes its resolution; a replacement configuration starts a new initial resolution.
- Loading and errors: keep the last successful children, or the initial
fields, while waiting. An Observable may emit multiple replacements; completion without a result keeps the current children. A synchronous throw, rejected Promise, or Observable error keeps the current children and reports a diagnostic. A subsequent dependency change can retry. This contract does not introduce a loading widget or a submission-blocking policy. - Controls: a result must be synchronized with controls and UI state before
rendering. Preserve existing values only for compatible controls with the
same path; remove controls for children that disappear. Child control names
must be unique within their form group, including across visual containers.
Invalid results, including duplicate names, retain the last synchronized
children and produce an
invalid-resultdiagnostic. Generated nested visual containers can declare their own resolvers; they are registered after their controls are ready and disposed when their parent result removes them.
The form builds a separate effective tree, available as renderedFields. Input
containers keep their original static fields; use fieldResolutions to inspect
resolved definitions and form.getRawValue() to read the actual submitted data.
Compatible controls retain user input across resolutions even when a new default
value is returned. Removed controls and their values leave the form.
This contract leaves options, optionGroups, resolveUi, validations,
asyncValidations, valueChanges, and the one-time asyncValue behavior
unchanged. Reusing the dependency engine for those APIs is a later step.
After initialization, inspect an existing container's resolution with a form reference:
const state = dynamicForm.fieldResolutions().get(container);
// state.status: 'waiting' | 'loading' | 'ready' | 'error'
// state.fields: last successful result, or initial fields before the first result
// state.diagnostic: code, message, paths, element and optional underlying errorDiagnostics are also reported to the console and repeated identical dependency diagnostics are suppressed. The engine is refreshed after form configuration updates and disposed when the form is destroyed. Context is read at each resolution but does not trigger resolutions by itself.
KlesFormDependencyEngine is also exported for standalone use. Construct it with
a root form and optionally a context getter, call update(elements) to register
containers or refresh silent control edits, and call destroy() when finished.
Its states Signal retains outcomes; resolved$ emits successful arrays and
diagnostics$ emits diagnostics. It never changes fields or controls itself.
Directives
- KlesComponentDirective -> Directive to inject component with value
- KlesDynamicFieldDirective -> Directive to inject component with IFieldConfig in FormGroup
Interfaces
IKlesFieldConfig
Interface field model
- type?: string -> Mapper type if(type && !component)=>type
- name: string -> Name Field (key for FormControlName)
- component?: Type<any> -> Component field
- id?: string -> Attribut html id
- label?: string -> Label field
- placeholder?: string -> Placeholder field
- tooltip?: string -> Tooltip field
- inputType?: string -> Input type
- min?: number | Date -> Min value for number input or date field
- max?: number | Date -> Max value for number input or date field
- maxLength?: number -> Max length for input value
- step?: number -> Define a step for number input
- options?: any[] | Subject<any[]> | Observable<any[]> | ((value?: string) => Observable<any[]>) -> List options for list component
- ngClass?: any -> ngclass for field
- ngStyle?: any -> ngStyle for field
- property?: string -> Property for field
- collections?: any -> Collections for subfield
- value?: any -> Value field
- asyncValue?: Observable<any> -> Async value field
- multiple?: boolean -> Multiple selection field
- disabled?: boolean -> Disabled field
- autocomplete?: boolean -> Autocomplete input field
- autocompleteComponent?: Type<\any> -> Autocomplete component to display in list option
- displayWith?: ((value: any) => string) | null -> Autocomplete display format
- panelWidth?: string | number -> Width for panel list option
- indeterminate?: boolean -> Indeterminate checkable component
- color?: string -> Material color
- icon?: string -> Material icon
- iconSvg?: string -> Svg icon
- textareaAutoSize?: { minRows?: number; maxRows?: number } -> Define a min and max number of rows for text area
- validations?: IKlesValidator<ValidatorFn>[] -> Array of validators for a field
- asyncValidations?: IKlesValidator<AsyncValidatorFn>[] -> Array of async validators for a field
- pipeTransform?: { pipe: PipeTransform, options?: any[] }[] -> Array of pipe to apply to value field
- direction?: 'row' | 'column' -> Direction for the display of the fields
- valueChanges?: ((field: IKlesFieldConfig, group: UntypedFormGroup, siblingField?: IKlesFieldConfig[], valueChanged?: any) => void) -> Emit each time the value is changing
- triggerComponent?: Type<any> -> Trigger component to customize trigger label in select
- virtualScroll?: boolean -> To activate virtual scroll
- itemSize?: number -> Item size for virtual scroll
- pending?: boolean -> If the value is pending
- searchKeys?: string[] -> List of keys for multiple searches
- updateOn?: 'change' | 'blur' | 'submit' -> Set the update on diferent moment
- debounceTime?: number -> Time in milliseconds before emit value after changed
- directive?: (new (ref: ViewContainerRef, field: IKlesField) => IKlesDirective) -> Set a new directive to the field
- visible?: boolean -> Set if the field is visible
- lazy?: boolean -> Set if the field is lazy
- buttonType?: 'submit' | 'button' | 'reset' -> Define the type of a button
- accept?: string ->
- dateOptions?: { adapter?: { class: Type<DateAdapter<any>>, deps?: any[] }, language: string, dateFormat: MatDateFormats } -> Define options for date field
- hint?: string -> Display as a mat-hint for the field
- clearable?: boolean -> Display a cross to clear the field
- clearableComponent?: Type<any> -> Define a new clearable component
- subComponents?: Type<any>[] -> Define sub components of a field
- autofocus?: boolean -> Set the autofocus on the field
- attribute?: EnumButtonAttribute -> To display material button design
- subscriptSizing?: SubscriptSizing ->
- nonNullable?: boolean -> Set if the field can be null
Fields
- KlesFormArrayComponent component that creates a form array of other components
- KlesFormBadgeComponent component to display a badge
- KlesFormFabComponent component to display a fab button
- KlesFormButtonComponent component to display a button
- KlesFormIconButtonComponent component to display an icon button
- KlesFormMiniFabComponent component to display a mini fab button
- KlesFormButtonToogleGroupComponent component to display a button group toggle
- KlesFormButtonCheckerComponent component to check error in form
- KlesFormButtonFileComponent button that take a file
- KlesFormCheckboxComponent component to display a checkbox
- KlesFormChipComponent component to display a chip
- KlesFormClearComponent component to clear a form
- KlesFormColorComponent component to select a color
- KlesFormDateComponent component to select a date
- KlesFieldAbstract abstract class to build field component
- KlesFormGroupComponent component that creates a form group of other components
- KlesFormIconComponent component to display a material icon
- KlesFormInputClearableComponent component with an input that is clearable
- KlesFormInputComponent component with an input
- KlesFormLineBreakComponent component to go to the next line
- KlesFormLinkComponent component that display a link
- KlesFormListFieldComponent component to display a list
- KlesFormRadioComponent component that display a material radio
- KlesFormRangeComponent component to select a date range
- KlesFormSelectComponent component to select a value in options
- KlesFormSelectLazySearchComponent component to select in lazy options that can be filtered
- KlesFormSelectSearchComponent component to select a value in options that can be filtered
- KlesFormSelectionListComponent component to create a list with elements that can be selected
- KlesFormSelectionListSearchComponent component to create a list with elements that can be selected and filtered
- KlesFormSlideToggleComponent component to display a group toggle
- KlesFormTextComponent component to display text
- KlesFormTextareaComponent component to display a text area
Install
npm
npm install @3kles/kles-material-dynamicforms --saveHow to use
In the module
import { KlesMaterialDynamicFormsModule } from '@3kles/kles-material-dynamicforms';
...
@NgModule({
imports: [
KlesMaterialDynamicFormsModule,
...
]
...
})Check the documentation to use component and directive.
Tests
npm install
npm test