Browse Source

refactoring

pull/24547/head
erdemcaygor 1 year ago
parent
commit
fbd7aa5aa3
  1. 74
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts
  2. 4
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.html
  3. 31
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts
  4. 7
      npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.models.ts

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

@ -1,20 +1,32 @@
import { import {
Component, Input, ViewChild, ViewContainerRef, OnChanges, SimpleChanges, Component,
ChangeDetectionStrategy, forwardRef, Type, Injector, effect, DestroyRef, inject ViewChild,
ViewContainerRef,
OnChanges,
SimpleChanges,
ChangeDetectionStrategy,
forwardRef,
Type,
Injector,
effect,
DestroyRef,
inject,
input,
ChangeDetectorRef,
} from '@angular/core'; } from '@angular/core';
import { import {
ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, ReactiveFormsModule ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, ReactiveFormsModule
} from '@angular/forms'; } from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
type MaybeCVA = Partial<ControlValueAccessor> & { setDisabledState?(d: boolean): void }; type controlValueAccessorLike = Partial<ControlValueAccessor> & { setDisabledState?(d: boolean): void };
type WithFormControlInput = { formControl?: FormControl }; type acceptsFormControl = { formControl?: FormControl };
@Component({ @Component({
selector: 'abp-dynamic-form-field-host', selector: 'abp-dynamic-form-field-host',
standalone: true,
imports: [CommonModule, ReactiveFormsModule], imports: [CommonModule, ReactiveFormsModule],
template: `<ng-template #vc></ng-template>`, template: `<ng-template #vcRef></ng-template>`,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: [{ providers: [{
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -23,39 +35,35 @@ type WithFormControlInput = { formControl?: FormControl };
}] }]
}) })
export class DynamicFieldHostComponent implements ControlValueAccessor, OnChanges { export class DynamicFieldHostComponent implements ControlValueAccessor, OnChanges {
@Input({ required: true }) component!: Type<any>; component = input<Type<any>>();
@Input() inputs: Record<string, any> = {}; // field, visible, vs. inputs = input<Record<string, any>>({});
@ViewChild('vc', { read: ViewContainerRef, static: true }) vc!: ViewContainerRef; @ViewChild('vcRef', { read: ViewContainerRef, static: true }) viewContainerRef!: ViewContainerRef;
private componentRef?: any;
private compRef?: any;
private onChange: (v: any) => void = () => {};
private onTouched: () => void = () => {};
private lastValue: any; private lastValue: any;
private disabled = false; private disabled = false;
// Eğer child CVA değilse kullanmak üzere bir iç kontrol (opsiyonel) // if child has not implemented ControlValueAccessor. Create form control
private innerControl = new FormControl<any>(null); private innerControl = new FormControl<any>(null);
readonly destroyRef = inject(DestroyRef);
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes['component']) { if (changes['component']) {
this.createChild(); this.createChild();
} else if (this.compRef && changes['inputs']) { } else if (this.componentRef && changes['inputs']) {
this.applyInputs(); this.applyInputs();
} }
} }
private createChild() { private createChild() {
this.vc.clear(); this.viewContainerRef.clear();
if (!this.component) return; if (!this.component()) return;
this.compRef = this.vc.createComponent(this.component);
// inputları ata this.componentRef = this.viewContainerRef.createComponent(this.component());
this.applyInputs(); this.applyInputs();
// ÇOCUK TIPINI KONTROL ET: CVA mı? const instance: any = this.componentRef.instance as controlValueAccessorLike & acceptsFormControl;
const instance: any = this.compRef.instance as MaybeCVA & WithFormControlInput;
if (this.isCVA(instance)) { if (this.isCVA(instance)) {
// Child CVA ise wrapper -> child delege // Child CVA ise wrapper -> child delege
@ -76,28 +84,27 @@ export class DynamicFieldHostComponent implements ControlValueAccessor, OnChange
if (this.lastValue !== undefined) { if (this.lastValue !== undefined) {
this.innerControl.setValue(this.lastValue, { emitEvent: false }); this.innerControl.setValue(this.lastValue, { emitEvent: false });
} }
this.innerControl.valueChanges.subscribe(v => this.onChange(v)); this.innerControl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(v => this.onChange(v));
this.innerControl.disabled ? null : (this.disabled && this.innerControl.disable({ emitEvent: false })); this.innerControl.disabled ? null : (this.disabled && this.innerControl.disable({ emitEvent: false }));
} else { } else {
// Son çare: valueChange EventEmitter’ı varsa bağla (konvansiyonel) // Son çare: valueChange EventEmitter’ı varsa bağla (konvansiyonel)
if ('valueChange' in instance && instance['valueChange']?.subscribe) { if ('valueChange' in instance && instance['valueChange']?.subscribe) {
instance['valueChange'].subscribe((v: any) => this.onChange(v)); instance['valueChange'].pipe(takeUntilDestroyed(this.destroyRef)).subscribe((v: any) => this.onChange(v));
} }
} }
} }
} }
private applyInputs() { private applyInputs() {
if (!this.compRef) return; if (!this.componentRef) return;
const inst = this.compRef.instance; const inst = this.componentRef.instance;
for (const [k, v] of Object.entries(this.inputs ?? {})) { for (const [k, v] of Object.entries(this.inputs ?? {})) {
inst[k] = v; inst[k] = v;
} }
// change detection tetiklenebilir: this.componentRef.changeDetectorRef?.markForCheck?.();
this.compRef.changeDetectorRef?.markForCheck?.();
} }
private isCVA(obj: any): obj is MaybeCVA { private isCVA(obj: any): obj is controlValueAccessorLike {
return obj && typeof obj.writeValue === 'function' && typeof obj.registerOnChange === 'function'; return obj && typeof obj.writeValue === 'function' && typeof obj.registerOnChange === 'function';
} }
@ -105,9 +112,9 @@ export class DynamicFieldHostComponent implements ControlValueAccessor, OnChange
writeValue(obj: any): void { writeValue(obj: any): void {
this.lastValue = obj; this.lastValue = obj;
if (!this.compRef) return; if (!this.componentRef) return;
const inst: any = this.compRef.instance as MaybeCVA & WithFormControlInput; const inst: any = this.componentRef.instance as controlValueAccessorLike & acceptsFormControl;
if (this.isCVA(inst)) { if (this.isCVA(inst)) {
inst.writeValue?.(obj); inst.writeValue?.(obj);
@ -116,14 +123,17 @@ export class DynamicFieldHostComponent implements ControlValueAccessor, OnChange
} }
} }
private onChange: (v: any) => void = () => {};
private onTouched: () => void = () => {};
registerOnChange(fn: any): void { this.onChange = fn; } registerOnChange(fn: any): void { this.onChange = fn; }
registerOnTouched(fn: any): void { this.onTouched = fn; } registerOnTouched(fn: any): void { this.onTouched = fn; }
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (!this.compRef) return; if (!this.componentRef) return;
const inst = this.compRef.instance as MaybeCVA & WithFormControlInput; const inst = this.componentRef.instance as controlValueAccessorLike & acceptsFormControl;
if (this.isCVA(inst) && inst.setDisabledState) { if (this.isCVA(inst) && inst.setDisabledState) {
inst.setDisabledState(isDisabled); inst.setDisabledState(isDisabled);

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

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

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

@ -7,19 +7,14 @@ import {
OnInit, OnInit,
DestroyRef, DestroyRef,
ChangeDetectorRef, ChangeDetectorRef,
effect, effect
contentChild,
ContentChildren,
QueryList,
AfterContentInit,
} from '@angular/core'; } from '@angular/core';
import { FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DynamicFormService } from './dynamic-form.service'; import { DynamicFormService } from './dynamic-form.service';
import { FormFieldConfig } from './dynamic-form.models'; import { ConditionalAction, FormFieldConfig } from './dynamic-form.models';
import { DynamicFormFieldComponent, DynamicInputDirective } from './dynamic-form-field'; import { DynamicFormFieldComponent, DynamicFieldHostComponent } from './dynamic-form-field';
import { DynamicFieldHostComponent } from './dynamic-form-field/dynamic-form-field-host.component';
@Component({ @Component({
selector: 'abp-dynamic-form', selector: 'abp-dynamic-form',
@ -32,11 +27,10 @@ import { DynamicFieldHostComponent } from './dynamic-form-field/dynamic-form-fie
CommonModule, CommonModule,
DynamicFormFieldComponent, DynamicFormFieldComponent,
ReactiveFormsModule, ReactiveFormsModule,
DynamicInputDirective,
DynamicFieldHostComponent, DynamicFieldHostComponent,
], ],
}) })
export class DynamicFormComponent implements OnInit, AfterContentInit { export class DynamicFormComponent implements OnInit {
fields = input<FormFieldConfig[]>([]); fields = input<FormFieldConfig[]>([]);
values = input<Record<string, any>>(); values = input<Record<string, any>>();
submitButtonText = input<string>('Submit'); submitButtonText = input<string>('Submit');
@ -47,21 +41,12 @@ export class DynamicFormComponent implements OnInit, AfterContentInit {
private dynamicFormService = inject(DynamicFormService); private dynamicFormService = inject(DynamicFormService);
readonly destroyRef = inject(DestroyRef); readonly destroyRef = inject(DestroyRef);
readonly changeDetectorRef = inject(ChangeDetectorRef); readonly changeDetectorRef = inject(ChangeDetectorRef);
@ContentChildren(DynamicInputDirective) dynamicInputs: QueryList<DynamicInputDirective>;
dynamicForm!: FormGroup; dynamicForm!: FormGroup;
fieldVisibility: { [key: string]: boolean } = {}; fieldVisibility: { [key: string]: boolean } = {};
ngOnInit() { ngOnInit() {
this.setupFormAndLogic(); this.setupFormAndLogic();
effect(() => {
console.log(this.values());
});
}
ngAfterContentInit() {
console.log(this.dynamicInputs.toArray());
} }
get sortedFields(): FormFieldConfig[] { get sortedFields(): FormFieldConfig[] {
@ -159,18 +144,18 @@ export class DynamicFormComponent implements OnInit, AfterContentInit {
const control = this.dynamicForm.get(fieldKey); const control = this.dynamicForm.get(fieldKey);
switch (action) { switch (action) {
case 'show': case ConditionalAction.SHOW:
this.fieldVisibility = { ...this.fieldVisibility, [fieldKey]: shouldApply }; this.fieldVisibility = { ...this.fieldVisibility, [fieldKey]: shouldApply };
break; break;
case 'hide': case ConditionalAction.HIDE:
this.fieldVisibility = { ...this.fieldVisibility, [fieldKey]: !shouldApply }; this.fieldVisibility = { ...this.fieldVisibility, [fieldKey]: !shouldApply };
break; break;
case 'enable': case ConditionalAction.ENABLE:
if (control) { if (control) {
shouldApply ? control.enable() : control.disable(); shouldApply ? control.enable() : control.disable();
} }
break; break;
case 'disable': case ConditionalAction.DISABLE:
if (control) { if (control) {
shouldApply ? control.disable() : control.enable(); shouldApply ? control.disable() : control.enable();
} }

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

@ -27,3 +27,10 @@ export interface ConditionalRule {
value: any; value: any;
action: 'show' | 'hide' | 'enable' | 'disable'; action: 'show' | 'hide' | 'enable' | 'disable';
} }
export enum ConditionalAction {
SHOW = 'show',
HIDE = 'hide',
ENABLE = 'enable',
DISABLE = 'disable'
}

Loading…
Cancel
Save