src/app/features/public-form/edit-public-form-columns/edit-public-form-columns.component.ts
CustomFormControlDirective<FieldGroup[]>
| changeDetection | ChangeDetectionStrategy.OnPush |
| providers |
EditPublicFormColumnsComponent
|
| selector | app-edit-public-form-columns |
| standalone | true |
| imports | |
| styleUrls | ./edit-public-form-columns.component.scss |
| templateUrl | ./edit-public-form-columns.component.html |
| styleUrl | ./edit-public-form-columns.component.scss |
No results matching.
Properties |
|
Methods |
Inputs |
Outputs |
| entity | |
Type : Entity
|
|
| formFieldConfig | |
Type : FormFieldConfig
|
|
| aria-describedby | |
Type : string
|
|
|
Inherited from
CustomFormControlDirective
|
|
| disabled | |
Type : boolean
|
|
|
Inherited from
CustomFormControlDirective
|
|
| ngControl | |
Type : any
|
|
Default value : inject(NgControl, { optional: true, self: true })
|
|
|
Inherited from
CustomFormControlDirective
|
|
| placeholder | |
Type : string
|
|
|
Inherited from
CustomFormControlDirective
|
|
| required | |
Type : boolean
|
|
|
Inherited from
CustomFormControlDirective
|
|
| value | |
Type : T
|
|
|
Inherited from
CustomFormControlDirective
|
|
| valueChange | |
Type : EventEmitter
|
|
|
Inherited from
CustomFormControlDirective
|
|
| updateValue | ||||||
updateValue(newConfig: FormConfig)
|
||||||
|
Parameters :
Returns :
void
|
| blur |
blur()
|
|
Inherited from
CustomFormControlDirective
|
|
Returns :
void
|
| focus |
focus()
|
|
Inherited from
CustomFormControlDirective
|
|
Returns :
void
|
| onContainerClick | ||||||
onContainerClick(event: MouseEvent)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| registerOnChange | ||||||
registerOnChange(fn: any)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| registerOnTouched | ||||||
registerOnTouched(fn: any)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| setDescribedByIds | ||||||
setDescribedByIds(ids: string[])
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| setDisabledState | ||||||
setDisabledState(isDisabled: boolean)
|
||||||
|
Inherited from
CustomFormControlDirective
|
||||||
|
Parameters :
Returns :
void
|
| writeValue | |||||||||||||||
writeValue(value: T, notifyFormControl: unknown)
|
|||||||||||||||
|
Inherited from
CustomFormControlDirective
|
|||||||||||||||
|
Implementation for Angular ControlValueAccessor interface that links the form control value to the component value
Parameters :
Returns :
void
|
| Optional entityConstructor |
Type : EntityConstructor
|
|
not set for configs in the multi form format, which hold no top-level entity type |
| Optional entityForm |
Type : AdminEntityFormComponent
|
Decorators :
@ViewChild(AdminEntityFormComponent)
|
| formConfig |
Type : FormConfig
|
| isDisabled |
Type : unknown
|
Default value : signal(false)
|
| controlType |
Type : string
|
Default value : "custom-control"
|
|
Inherited from
CustomFormControlDirective
|
| elementRef |
Type : unknown
|
Default value : inject<ElementRef<HTMLElement>>(ElementRef)
|
|
Inherited from
CustomFormControlDirective
|
| Readonly enabled |
Type : Signal<boolean>
|
Default value : computed(() => !this._disabled())
|
|
Inherited from
CustomFormControlDirective
|
|
Whether the control is currently enabled, as a signal (tracks disabled). |
| errorStateMatcher |
Type : unknown
|
Default value : inject(ErrorStateMatcher)
|
|
Inherited from
CustomFormControlDirective
|
| id |
Type : unknown
|
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
|
|
Inherited from
CustomFormControlDirective
|
| Static nextId |
Type : number
|
Default value : 0
|
|
Inherited from
CustomFormControlDirective
|
| onChange |
Type : unknown
|
Default value : () => {...}
|
|
Inherited from
CustomFormControlDirective
|
| onTouched |
Type : unknown
|
Default value : () => {...}
|
|
Inherited from
CustomFormControlDirective
|
| parentForm |
Type : unknown
|
Default value : inject(NgForm, { optional: true })
|
|
Inherited from
CustomFormControlDirective
|
| parentFormGroup |
Type : unknown
|
Default value : inject(FormGroupDirective, { optional: true })
|
|
Inherited from
CustomFormControlDirective
|
| stateChanges |
Type : unknown
|
Default value : new Subject<void>()
|
|
Inherited from
CustomFormControlDirective
|
| Readonly valueSignal |
Type : Signal<T>
|
Default value : computed(() => this._value())
|
|
Inherited from
CustomFormControlDirective
|
|
The current value of the control as a signal.
Authoritative in both modes: it reflects the bound |
import { EditComponent } from "#src/app/core/entity/entity-field-edit/dynamic-edit/edit-component.interface";
import {
ChangeDetectionStrategy,
Component,
inject,
input,
OnInit,
signal,
ViewChild,
} from "@angular/core";
import { MatFormFieldControl } from "@angular/material/form-field";
import { AdminEntityFormComponent } from "app/core/admin/admin-entity-details/admin-entity-form/admin-entity-form.component";
import { CustomFormControlDirective } from "app/core/common-components/basic-autocomplete/custom-form-control.directive";
import { FormFieldConfig } from "app/core/common-components/entity-form/FormConfig";
import { DynamicComponent } from "app/core/config/dynamic-components/dynamic-component.decorator";
import { FieldGroup } from "app/core/entity-details/form/field-group";
import { FormConfig } from "app/core/entity-details/form/form.component";
import { EntityRegistry } from "app/core/entity/database-entity.decorator";
import { Entity, EntityConstructor } from "app/core/entity/model/entity";
import { PublicFormConfig } from "../public-form-config";
import { migratePublicFormConfig } from "../public-form.component";
import { HintBoxComponent } from "#src/app/core/common-components/hint-box/hint-box.component";
import { EntityMapperService } from "app/core/entity/entity-mapper/entity-mapper.service";
import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
import { PublicFormsService } from "../public-forms.service";
@UntilDestroy()
@DynamicComponent("EditPublicFormColumns")
@Component({
selector: "app-edit-public-form-columns",
imports: [AdminEntityFormComponent, HintBoxComponent],
templateUrl: "./edit-public-form-columns.component.html",
styleUrl: "./edit-public-form-columns.component.scss",
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: MatFormFieldControl,
useExisting: EditPublicFormColumnsComponent,
},
],
})
export class EditPublicFormColumnsComponent
extends CustomFormControlDirective<FieldGroup[]>
implements OnInit, EditComponent
{
formFieldConfig = input<FormFieldConfig>();
entity = input<Entity>();
@ViewChild(AdminEntityFormComponent) entityForm?: AdminEntityFormComponent;
/** not set for configs in the multi form format, which hold no top-level entity type */
entityConstructor?: EntityConstructor;
formConfig: FormConfig;
private originalFormConfig: FormConfig;
// Signal to track disabled state for immediate UI updates
isDisabled = signal(false);
private entities = inject(EntityRegistry);
private readonly entityMapper = inject(EntityMapperService);
private readonly publicFormsService = inject(PublicFormsService);
ngOnInit() {
const entity = this.entity();
if (entity) {
// configs in the multi form format do not hold a top-level entity type
this.entityConstructor = entity["entity"]
? this.entities.get(entity["entity"])
: undefined;
const publicFormConfig: PublicFormConfig = migratePublicFormConfig({
columns: this.formControl.getRawValue(),
} as Partial<PublicFormConfig> as PublicFormConfig);
this.formConfig = {
fieldGroups: publicFormConfig.columns,
};
this.originalFormConfig = JSON.parse(JSON.stringify(this.formConfig));
this.setupFormStateDetection();
// Set initial disabled state
this.isDisabled.set(this.formControl.disabled);
this.subscribeToPublicFormConfigSave();
}
}
/**
* Subscribe to entity save events to persist custom fields to global schema
* after the PublicFormConfig is saved.
*/
private subscribeToPublicFormConfigSave() {
this.entityMapper
.receiveUpdates(PublicFormConfig)
.pipe(untilDestroyed(this))
.subscribe(async (update) => {
await this.publicFormsService.saveCustomFieldsToEntityConfig(
update.entity,
);
});
}
updateValue(newConfig: FormConfig) {
// setTimeout needed for change detection of disabling tabs
setTimeout(() => this.formControl.setValue(newConfig.fieldGroups));
this.formControl.markAsDirty();
}
/**
* Setup form state detection for cancel vs save operations
*/
private setupFormStateDetection(): void {
let wasDirty = false;
let lastValue: FieldGroup[] | null = null;
// Listen to form control status and value changes to detect cancel operations
this.formControl.statusChanges.subscribe(() => {
const isDirty = this.formControl.dirty;
const isPristine = this.formControl.pristine;
const currentValue = this.formControl.value;
// If form was dirty and now becomes pristine
if (wasDirty && isPristine) {
const normalizedCurrentValue = this.normalizeFieldGroups(
currentValue || [],
);
const normalizedOriginalValue = this.normalizeFieldGroups(
this.originalFormConfig.fieldGroups || [],
);
// Check if the value was reverted to original (cancel)
const isValueRevertedToOriginal =
JSON.stringify(normalizedCurrentValue) ===
JSON.stringify(normalizedOriginalValue);
if (isValueRevertedToOriginal) {
// Reset UI to original configuration immediately
this.formConfig = JSON.parse(JSON.stringify(this.originalFormConfig));
}
}
wasDirty = isDirty;
lastValue = currentValue;
this.isDisabled.set(this.formControl.disabled);
});
}
// Normalize both values for comparison (handle missing header property)
private normalizeFieldGroups = (fieldGroups: FieldGroup[]) => {
return fieldGroups.map((group) => ({
...group,
header: group.header || null,
}));
};
}
<app-hint-box i18n>
You can edit how users will see the details of this public form. To edit
fields, click on the Edit button, make your changes, and then save. These
changes will reflect on the public form.
<br />
Drag and drop fields and sections in this preview of a profile view. The
editor below closely resembles how the form will look for users later. Forms
show all fields below each other not in multiple columns, however.
<br />
</app-hint-box>
<div class="column-container">
<app-admin-entity-form
[config]="formConfig"
(configChange)="updateValue($event)"
[entityType]="entityConstructor"
[isDisabled]="isDisabled()"
[updateEntitySchema]="false"
>
</app-admin-entity-form>
</div>
./edit-public-form-columns.component.scss
.column-container {
overflow: hidden;
}