Use custom component to build complex form for the control.
For example, we need a component that can handle file input. First, create a component called InputFileComponent
and extends it with CustomControlComponent
, then override the control
and assign it with an instance of AbstractControl
.
import { CustomControlComponent } from 'ng-dynamic-json-form';
@Component({...})
export class InputFileComponent extends CustomControlComponent {
override control = new FormControl<File | null>(null);
}
The
CustomControlComponent
implements ControlValueAccessor and Validator. Thecontrol
is use to connect with the parent control. Hence, it is strongly recommended to usecontrol
to bind with the FormGroup or FormControl in this component, so we don’t need to rewrite the logic inside the methodswriteValue()
andregisterOnChange()
.
@Component({...}) export class CustomControlComponent implements ControlValueAccessor, Validator { ... writeValue(obj: any): void { this.control?.patchValue(obj); } registerOnChange(fn: any): void { this.control?.valueChanges.subscribe(fn); } }
NOTE It's not neccessary to provide
NG_VALUE_ACCESSOR
andNG_VALIDATORS
, if the component is only use inside theNgDynamicJsonFormComponent
Next, bind the control
to the input and add input
event listener.
<input type="file" [formControl]="control" (input)="onFileSelect($event)" />
onFileSelect(e: Event): void {
if (this.control.disabled) return;
const input = e.target as HTMLInputElement;
const files = Array.from(input.files ?? []);
input.value = '';
if (files.length > 0) {
this.control.setValue(files[0]);
}
}
There is
data
property that’s use to provide the config for this component.
@if (!data?.readonly) { <input type="file" [formControl]="control" (input)="onFileSelect($event)" /> } @else { <span>{{ control.value.name }}</span> }
For other properties, use
data.props
property. See Props.
<input type="file" [formControl]="control" [accept]="data?.props?.accept ?? '*'" (input)="onFileSelect($event)" />
Now our InputFileComponent
is ready to use as a custom component. Add it to the customComponents
, and if any of the formControlName
is matched with the key inside customComponents
, that component will be use for that control.
<ng-dynamic-json-form
[configs]="configs"
[customComponents]="customComponents"
></ng-dynamic-json-form>
export class AppComponent {
configs: FormControlConfig[] = [
{
formControlName: "name",
label: "Name",
},
{
formControlName: "inputFile",
label: "File upload",
},
];
customComponents: CustomComponents = {
inputFile: InputFileComponent,
};
}
The control to bind with the view and connect with the parent form. Can be assigned with any of the FormControl
, FormGroup
or FormArray
.
The config for this component.
The UntypedFormGroup
of the current ng-dynamic-json-form
instance.
The hideErrorMessage
value from root component. This is useful to control the visibility of errors.
Call after the options is successfully fetched from API endpoint. Override to take control of how and when the data should be used.
onOptionsGet(data: OptionItem[]): void {
if (!this.data || !this.data.options) {
return;
}
this.data.options.data = data;
}
markAsDirty(): void {}
markAsPristine(): void {}
markAsTouched(): void {}
markAsUntouched(): void {}
setErrors(errors: ValidationErrors | null): void {}
Sometimes, if creating a component is way too much, we can use ng-template.
<ng-dynamic-json-form
[configs]="..."
[customTemplates]="{
inputFile: inputFileTemplate
}"
>
<ng-template #inputFileTemplate let-control="control" let-data="data">
@if (!data?.readonly) {
<input type="file" [formControl]="control" (input)="onFileSelect($event)" />
} @else {
<span>{{ control.value.name }}</span>
}
</ng-template>
</ng-dynamic-json-form>
The variables available for the ng-template are:
Name | Type | Description |
---|---|---|
control | AbstractControl | The FormControl for this input. |
data | FormControlConfig | The config for this input. |
hostForm | UntypedFormGroup | The FormGroup of the current ng-dynamic-json-form instance. |
hideErrorMessage | boolean | The value of hideErrorMessage from root component. |