Browse Source

dynamic form field host component added to custom form input

pull/24547/head
erdemcaygor 11 months ago
parent
commit
a9486e92c4
  1. 134
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts
  2. 1
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/index.ts
  3. 8
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.html
  4. 24
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts
  5. 2
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.models.ts

134
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts

@ -0,0 +1,134 @@
import {
Component, Input, ViewChild, ViewContainerRef, OnChanges, SimpleChanges,
ChangeDetectionStrategy, forwardRef, Type, Injector, effect, DestroyRef, inject
} from '@angular/core';
import {
ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, ReactiveFormsModule
} from '@angular/forms';
import { CommonModule } from '@angular/common';
type MaybeCVA = Partial<ControlValueAccessor> & { setDisabledState?(d: boolean): void };
type WithFormControlInput = { formControl?: FormControl };
@Component({
selector: 'abp-dynamic-form-field-host',
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
template: `<ng-template #vc></ng-template>`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DynamicFieldHostComponent),
multi: true
}]
})
export class DynamicFieldHostComponent implements ControlValueAccessor, OnChanges {
@Input({ required: true }) component!: Type<any>;
@Input() inputs: Record<string, any> = {}; // field, visible, vs.
@ViewChild('vc', { read: ViewContainerRef, static: true }) vc!: ViewContainerRef;
private compRef?: any;
private onChange: (v: any) => void = () => {};
private onTouched: () => void = () => {};
private lastValue: any;
private disabled = false;
// Eğer child CVA değilse kullanmak üzere bir iç kontrol (opsiyonel)
private innerControl = new FormControl<any>(null);
ngOnChanges(changes: SimpleChanges): void {
if (changes['component']) {
this.createChild();
} else if (this.compRef && changes['inputs']) {
this.applyInputs();
}
}
private createChild() {
this.vc.clear();
if (!this.component) return;
this.compRef = this.vc.createComponent(this.component);
// inputları ata
this.applyInputs();
// ÇOCUK TIPINI KONTROL ET: CVA mı?
const instance: any = this.compRef.instance as MaybeCVA & WithFormControlInput;
if (this.isCVA(instance)) {
// Child CVA ise wrapper -> child delege
instance.registerOnChange?.((v: any) => this.onChange(v));
instance.registerOnTouched?.(() => this.onTouched());
if (this.disabled && instance.setDisabledState) {
instance.setDisabledState(true);
}
// İlk değeri ilet
if (this.lastValue !== undefined) {
instance.writeValue?.(this.lastValue);
}
} else {
// Child CVA değilse, formControl input’u varsa köprüle
if ('formControl' in instance) {
instance.formControl = this.innerControl;
// son değeri uygula
if (this.lastValue !== undefined) {
this.innerControl.setValue(this.lastValue, { emitEvent: false });
}
this.innerControl.valueChanges.subscribe(v => this.onChange(v));
this.innerControl.disabled ? null : (this.disabled && this.innerControl.disable({ emitEvent: false }));
} else {
// Son çare: valueChange EventEmitter’ı varsa bağla (konvansiyonel)
if ('valueChange' in instance && instance['valueChange']?.subscribe) {
instance['valueChange'].subscribe((v: any) => this.onChange(v));
}
}
}
}
private applyInputs() {
if (!this.compRef) return;
const inst = this.compRef.instance;
for (const [k, v] of Object.entries(this.inputs ?? {})) {
inst[k] = v;
}
// change detection tetiklenebilir:
this.compRef.changeDetectorRef?.markForCheck?.();
}
private isCVA(obj: any): obj is MaybeCVA {
return obj && typeof obj.writeValue === 'function' && typeof obj.registerOnChange === 'function';
}
/* --------- CVA (wrapper) --------- */
writeValue(obj: any): void {
this.lastValue = obj;
if (!this.compRef) return;
const inst: any = this.compRef.instance as MaybeCVA & WithFormControlInput;
if (this.isCVA(inst)) {
inst.writeValue?.(obj);
} else if ('formControl' in inst && inst.formControl instanceof FormControl) {
inst.formControl.setValue(obj, { emitEvent: false });
}
}
registerOnChange(fn: any): void { this.onChange = fn; }
registerOnTouched(fn: any): void { this.onTouched = fn; }
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (!this.compRef) return;
const inst = this.compRef.instance as MaybeCVA & WithFormControlInput;
if (this.isCVA(inst) && inst.setDisabledState) {
inst.setDisabledState(isDisabled);
} else if ('formControl' in inst && inst.formControl instanceof FormControl) {
isDisabled ? inst.formControl.disable({ emitEvent: false }) : inst.formControl.enable({ emitEvent: false });
}
}
}

1
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/index.ts

@ -1 +1,2 @@
export * from './dynamic-form-field.component';
export * from './dynamic-form-field-host.component';

8
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.html

@ -3,11 +3,19 @@
<div class="row">
@for (field of sortedFields; track field.key) {
<div [ngClass]="'col-md-' + (field.gridSize || 12)">
@if (field.component) {
<app-dynamic-field-host
[component]="field.component"
[inputs]="{ field: field, visible: isFieldVisible(field) }"
formControlName="{{ field.key }}">
</app-dynamic-field-host>
} @else {
<abp-dynamic-form-field
[field]="field"
[formControlName]="field.key"
[visible]="isFieldVisible(field)">
</abp-dynamic-form-field>
}
</div>
}
</div>

24
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts

@ -8,13 +8,18 @@ import {
DestroyRef,
ChangeDetectorRef,
effect,
contentChild,
ContentChildren,
QueryList,
AfterContentInit,
} from '@angular/core';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DynamicFormService } from './dynamic-form.service';
import { FormFieldConfig } from './dynamic-form.models';
import { DynamicFormFieldComponent } from './dynamic-form-field';
import { DynamicFormFieldComponent, DynamicInputDirective } from './dynamic-form-field';
import { DynamicFieldHostComponent } from './dynamic-form-field/dynamic-form-field-host.component';
@Component({
selector: 'abp-dynamic-form',
@ -23,9 +28,15 @@ import { DynamicFormFieldComponent } from './dynamic-form-field';
host: { class: 'abp-dynamic-form' },
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'abpDynamicForm',
imports: [CommonModule, DynamicFormFieldComponent, ReactiveFormsModule],
imports: [
CommonModule,
DynamicFormFieldComponent,
ReactiveFormsModule,
DynamicInputDirective,
DynamicFieldHostComponent,
],
})
export class DynamicFormComponent implements OnInit {
export class DynamicFormComponent implements OnInit, AfterContentInit {
fields = input<FormFieldConfig[]>([]);
values = input<Record<string, any>>();
submitButtonText = input<string>('Submit');
@ -36,6 +47,7 @@ export class DynamicFormComponent implements OnInit {
private dynamicFormService = inject(DynamicFormService);
readonly destroyRef = inject(DestroyRef);
readonly changeDetectorRef = inject(ChangeDetectorRef);
@ContentChildren(DynamicInputDirective) dynamicInputs: QueryList<DynamicInputDirective>;
dynamicForm!: FormGroup;
fieldVisibility: { [key: string]: boolean } = {};
@ -48,6 +60,10 @@ export class DynamicFormComponent implements OnInit {
});
}
ngAfterContentInit() {
console.log(this.dynamicInputs.toArray());
}
get sortedFields(): FormFieldConfig[] {
return this.fields().sort((a, b) => (a.order || 0) - (b.order || 0));
}
@ -77,7 +93,7 @@ export class DynamicFormComponent implements OnInit {
const initialValues: { [key: string]: any } = this.dynamicFormService.getInitialValues(
this.fields(),
);
this.dynamicForm.reset({...initialValues});
this.dynamicForm.reset({ ...initialValues });
this.dynamicForm.markAsUntouched();
this.dynamicForm.markAsPristine();
this.changeDetectorRef.markForCheck();

2
npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.models.ts

@ -1,3 +1,4 @@
export interface FormFieldConfig {
key: string;
value?: any;
@ -11,6 +12,7 @@ export interface FormFieldConfig {
conditionalLogic?: ConditionalRule[];
order?: number;
gridSize?: number;
component?: any;
}
export interface ValidatorConfig {

Loading…
Cancel
Save