src/app/features/public-form/edit-prefilled-values/edit-prefilled-values.component.ts

Extends

CustomFormControlDirective<Record<string, DefaultValueConfig>>

Implements

OnInit EditComponent

Metadata

Relationships

Used by

No results matching.

Index

Properties
Methods
Inputs
Outputs
Accessors

Inputs

entity
Type : Entity
formFieldConfig
Type : FormFieldConfig
aria-describedby
Type : string
disabled
Type : boolean
ngControl
Type : any
Default value : inject(NgControl, { optional: true, self: true })
placeholder
Type : string
required
Type : boolean
value
Type : T

Outputs

valueChange
Type : EventEmitter

Methods

addPrefilledFields
addPrefilledFields()
Returns : void
getSchemaField
getSchemaField(fieldId: string)
Parameters :
Name Type Optional
fieldId string No
Returns : EntitySchemaField
removePrefilledFields
removePrefilledFields(index: number)
Parameters :
Name Type Optional
index number No
Returns : void
blur
blur()
Returns : void
focus
focus()
Returns : void
onContainerClick
onContainerClick(event: MouseEvent)
Parameters :
Name Type Optional
event MouseEvent No
Returns : void
registerOnChange
registerOnChange(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnTouched
registerOnTouched(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
setDescribedByIds
setDescribedByIds(ids: string[])
Parameters :
Name Type Optional
ids string[] No
Returns : void
setDisabledState
setDisabledState(isDisabled: boolean)
Parameters :
Name Type Optional
isDisabled boolean No
Returns : void
writeValue
writeValue(value: T, notifyFormControl: unknown)

Implementation for Angular ControlValueAccessor interface that links the form control value to the component value

Parameters :
Name Type Optional Default value Description
value T No

The new value to set

notifyFormControl unknown No false

Whether to notify the FormControl of this change (for internal updates)

Returns : void

Properties

Optional entityConstructor
Type : EntityConstructor

not set for configs in the multi form format, which hold no top-level entity type

entitySchemaField
Type : EntitySchemaField
Readonly isDisabled
Type : unknown
Default value : signal(false)
prefilledValueSettings
Type : unknown
Default value : this.fb.group({ prefilledValue: this.fb.array([]), })
controlType
Type : string
Default value : "custom-control"
elementRef
Type : unknown
Default value : inject<ElementRef<HTMLElement>>(ElementRef)
Readonly enabled
Type : Signal<boolean>
Default value : computed(() => !this._disabled())

Whether the control is currently enabled, as a signal (tracks disabled).

errorStateMatcher
Type : unknown
Default value : inject(ErrorStateMatcher)
id
Type : unknown
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
Static nextId
Type : number
Default value : 0
onChange
Type : unknown
Default value : () => {...}
onTouched
Type : unknown
Default value : () => {...}
parentForm
Type : unknown
Default value : inject(NgForm, { optional: true })
parentFormGroup
Type : unknown
Default value : inject(FormGroupDirective, { optional: true })
stateChanges
Type : unknown
Default value : new Subject<void>()
Readonly valueSignal
Type : Signal<T>
Default value : computed(() => this._value())

The current value of the control as a signal. Authoritative in both modes: it reflects the bound FormControl (synced in ngDoCheck) as well as [(value)] / writeValue updates.

Accessors

prefilledValues
getprefilledValues()
import { EditComponent } from "#src/app/core/entity/entity-field-edit/dynamic-edit/edit-component.interface";
import {
  ChangeDetectionStrategy,
  Component,
  inject,
  input,
  OnInit,
  signal,
} from "@angular/core";
import {
  AbstractControl,
  FormArray,
  FormBuilder,
  ReactiveFormsModule,
  ValidatorFn,
  ValidationErrors,
  Validators,
} from "@angular/forms";
import { MatButtonModule } from "@angular/material/button";
import {
  MatFormFieldControl,
  MatFormFieldModule,
} from "@angular/material/form-field";
import { MatSelectModule } from "@angular/material/select";
import { MatTooltipModule } from "@angular/material/tooltip";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { CustomFormControlDirective } from "app/core/common-components/basic-autocomplete/custom-form-control.directive";
import { FormFieldConfig } from "app/core/common-components/entity-form/FormConfig";
import { HelpButtonComponent } from "app/core/common-components/help-button/help-button.component";
import { DynamicComponent } from "app/core/config/dynamic-components/dynamic-component.decorator";
import { EntityRegistry } from "app/core/entity/database-entity.decorator";
import { EntityFieldSelectComponent } from "app/core/entity/entity-field-select/entity-field-select.component";
import { Entity, EntityConstructor } from "app/core/entity/model/entity";
import { EntitySchemaField } from "app/core/entity/schema/entity-schema-field";
import { AdminDefaultValueComponent } from "../../../core/default-values/admin-default-value/admin-default-value.component";
import { DefaultValueConfig } from "../../../core/default-values/default-value-config";

const defaultValueCompleteValidator: ValidatorFn = (
  control: AbstractControl,
): ValidationErrors | null => {
  const val: DefaultValueConfig = control.value;
  if (!val) {
    return { incompleteDefaultValue: true };
  }
  if (val.mode && !val.config) {
    return { incompleteDefaultValue: true };
  }
  if (
    val.config &&
    typeof val.config === "object" &&
    "value" in val.config &&
    (val.config as { value: unknown }).value == null
  ) {
    return { incompleteDefaultValue: true };
  }
  return null;
};

@DynamicComponent("EditPrefilledValuesComponent")
@Component({
  selector: "app-edit-prefilled-values",
  standalone: true,
  imports: [
    AdminDefaultValueComponent,
    ReactiveFormsModule,
    MatFormFieldModule,
    MatSelectModule,
    MatTooltipModule,
    HelpButtonComponent,
    FontAwesomeModule,
    MatButtonModule,
    EntityFieldSelectComponent,
  ],
  templateUrl: "./edit-prefilled-values.component.html",
  styleUrls: ["./edit-prefilled-values.component.scss"],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    { provide: MatFormFieldControl, useExisting: EditPrefilledValuesComponent },
  ],
})
export class EditPrefilledValuesComponent
  extends CustomFormControlDirective<Record<string, DefaultValueConfig>>
  implements OnInit, EditComponent
{
  formFieldConfig = input<FormFieldConfig>();
  entity = input<Entity>();

  /** not set for configs in the multi form format, which hold no top-level entity type */
  entityConstructor?: EntityConstructor;
  entitySchemaField: EntitySchemaField;

  private readonly entities = inject(EntityRegistry);
  private readonly fb = inject(FormBuilder);

  readonly isDisabled = signal(false);

  prefilledValueSettings = this.fb.group({
    prefilledValue: this.fb.array([]),
  });

  ngOnInit(): void {
    const entity = this.entity();
    if (!entity) return;

    // configs in the multi form format do not hold a top-level entity type
    this.entityConstructor = entity["entity"]
      ? this.entities.get(entity["entity"])
      : undefined;
    this.initializePrefilledValues();
    this.prefilledValueSettings.valueChanges.subscribe((value) =>
      this.updateFieldGroups(value as { prefilledValue: PrefilledValue[] }),
    );

    this.formControl.statusChanges.subscribe(() => {
      const disabled = this.formControl.disabled;
      this.isDisabled.set(disabled);
      if (disabled) {
        this.prefilledValueSettings.disable({ emitEvent: false });
      } else {
        this.prefilledValueSettings.enable({ emitEvent: false });
      }
    });

    if (this.formControl.disabled) {
      this.isDisabled.set(true);
      this.prefilledValueSettings.disable({ emitEvent: false });
    }
  }

  get prefilledValues(): FormArray {
    return this.prefilledValueSettings.get("prefilledValue") as FormArray;
  }

  private initializePrefilledValues(): void {
    let fields = this.formControl.value;

    if (fields && typeof fields === "object" && !Array.isArray(fields)) {
      for (const [fieldId, defVal] of Object.entries(
        fields as Record<string, DefaultValueConfig>,
      )) {
        this.prefilledValues.push(
          this.fb.group({
            field: [fieldId, Validators.required],
            defaultValue: [defVal, defaultValueCompleteValidator],
          }),
        );
      }
      return;
    }
  }

  addPrefilledFields(): void {
    this.prefilledValues.push(
      this.fb.group({
        field: ["", Validators.required],
        defaultValue: [{ mode: "static" }, defaultValueCompleteValidator],
      }),
    );
  }

  removePrefilledFields(index: number): void {
    if (index < 0 || index >= this.prefilledValues.length) {
      return;
    }

    this.prefilledValues.removeAt(index);
    this.formControl.markAsDirty();
  }

  getSchemaField(fieldId: string): EntitySchemaField {
    return this.entityConstructor?.schema.get(fieldId);
  }

  private updateFieldGroups(value: { prefilledValue: PrefilledValue[] }): void {
    if (!value?.prefilledValue) return;
    if (this.prefilledValueSettings.invalid) {
      this.formControl.setErrors({ invalid: true });
      this.prefilledValueSettings.markAllAsTouched();
      return;
    }

    const updatedFields: Record<string, DefaultValueConfig> = {};
    value.prefilledValue.forEach(({ field, defaultValue }) => {
      if (field && defaultValue != null) updatedFields[field] = defaultValue;
    });

    const hasChanged =
      JSON.stringify(updatedFields) !==
      JSON.stringify(this.formControl.value ?? {});
    this.formControl.setValue(updatedFields);
    if (hasChanged) {
      this.formControl.markAsDirty();
    }
  }
}

interface PrefilledValue {
  field: string;
  defaultValue: DefaultValueConfig;
  hideFromForm?: boolean;
}
<div
  class="defaultvalue-container padding-regular margin-bottom-small"
  [class.disabled]="isDisabled()"
>
  <!-- without an entity type there are no fields to pick a value for -->
  @if (entityConstructor) {
    <form [formGroup]="prefilledValueSettings">
      <div formArrayName="prefilledValue">
        @for (
          fieldGroup of prefilledValues.controls;
          track $index;
          let i = $index
        ) {
          <div
            [formGroupName]="i"
            class="flex-row flex-wrap align-center gap-regular padding-regular margin-top-regular mat-elevation-z2 container"
          >
            <div class="flex-row align-center">
              <mat-form-field class="full-width-field" floatLabel="always">
                <mat-label i18n>Field</mat-label>
                <app-entity-field-select
                  [entityType]="entityConstructor"
                  formControlName="field"
                />
              </mat-form-field>
              <app-help-button
                text="Select a field for which you want to set a default value."
                i18n-text
              ></app-help-button>
            </div>
            @if (fieldGroup.get("field").value) {
              <div class="flex-grow">
                <div class="full-width-field">
                  <app-admin-default-value
                    formControlName="defaultValue"
                    [entityType]="entityConstructor"
                    [entitySchemaField]="
                      getSchemaField(fieldGroup.get('field').value)
                    "
                  ></app-admin-default-value>
                </div>
              </div>
            }
            <button
              mat-icon-button
              [disabled]="isDisabled()"
              (click)="removePrefilledFields(i)"
              matTooltip="Remove default value"
              i18n-matTooltip
            >
              <fa-icon icon="trash"></fa-icon>
            </button>
          </div>
        }
      </div>
    </form>

    <div class="add-new-defaultvalue-field">
      <button
        mat-stroked-button
        class="add-new-defaultvalue-button"
        color="accent"
        (click)="addPrefilledFields()"
        [disabled]="isDisabled() || prefilledValues.invalid"
        matTooltip="Add a fixed value for another field"
        i18n-matTooltip
      >
        <fa-icon
          aria-hidden="true"
          icon="plus-circle"
          class="standard-icon-with-text"
        ></fa-icon>
        <span i18n>Add pre-filled value</span>
      </button>
    </div>
  }
</div>

./edit-prefilled-values.component.scss

@use "variables/colors";
@use "variables/sizes";

.defaultvalue-container {
  overflow: hidden;

  &.disabled {
    opacity: 0.5;
    pointer-events: none;
  }
}

.container {
  border-radius: sizes.$regular;
}

.add-new-defaultvalue-field {
  display: flex;
  justify-content: center;
  margin-top: 30px;
}

.add-new-defaultvalue-button {
  width: 50%;
  border-radius: 20px;
  padding: 22px;
  background-color: aliceblue;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""