Skip to content

Filter Hidden Fields on Submit #66718

@ThiloAschebrock

Description

@ThiloAschebrock

Which @angular/* package(s) are relevant/related to the feature request?

forms

Description

When submitting forms built with signal forms, one might want to exclude hidden fields from the submitted data. Currently there's no built-in way to get only visible field values.

Workarounds require manual mapping for known structures or have to rely on internal APIs. Especially, the transversal of a field tree of unknown structure is difficult when relying on the public API.

Proposed solution

Option 1: Expose internal helper to public API

Having access to FieldNode could significantly simplify the transversal of a nested field tree:

export function toVisibleValue<T>(formTree: FieldTree<T>): DeepPartial<T> {
  const node = formTree as FieldNode;
  // Clean access to node.structure.children()
}

Option 2: Provide high-level utilities

For example

export function toVisibleValue<T>(formTree: FieldTree<T>): DeepPartial<T>;

Alternatives considered

Manual Collection for Known Forms

const visibleData = {
  name: this.form.name().hidden() ? undefined : this.form.name.value(),
};

Type-safe but not reusable. Requires rewriting for each form structure.

Manual Implementation with Internal APIs

type DeepPartial<T> = T extends (infer U)[]
  ? DeepPartial<U>[]
  : T extends ReadonlyArray<infer U>
    ? ReadonlyArray<DeepPartial<U>>
    : T extends object
      ? { [K in keyof T]?: DeepPartial<T[K]> }
      : T;

function toVisableValue<T>(
  form: FieldTree<T>,
): DeepPartial<T> | undefined {
  const fieldState = form();

  if (fieldState.hidden()) {
    return undefined;
  }

  const childKeys = Object.keys(form).filter((key) => key !== 'value');

  if (!childKeys.length) {
    return fieldState.value() as DeepPartial<T>;
  }

  const fieldValue = fieldState.value();

  if (Array.isArray(fieldValue)) {
    const result: unknown[] = [];
    for (const key of childKeys) {
      const processed = processChildField(key, form);
      if (processed !== undefined) {
        result.push(processed);
      }
    }
    return result as DeepPartial<T>;
  }

  const result: Record<string, unknown> = {};
  for (const key of childKeys) {
    const processed = processChildField(key, form);
    if (processed !== undefined) {
      result[key] = processed;
    }
  }
  return result as DeepPartial<T>;
}

function processChildField<T>(
  key: string,
  fieldTree: FieldTree<T>,
): DeepPartial<unknown> | undefined {
  const childField = (fieldTree as Record<string, unknown>)[key];
  if (!childField) {
    return undefined;
  }
  return toVisibleValue(childField as FieldTree<unknown>);
}

Works but fragile and may break with Angular updates.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

No status

Relationships

None yet

Development

No branches or pull requests

Issue actions