Input

The <smart-input> component renders a form input for a single model field by delegating to a sub-component chosen by FieldType. It selects a sub-component based on the FieldType resolved from @Field() decorator metadata via an internal baseMap and an optional extendMap injected via INPUT_FIELD_COMPONENTS_TOKEN. Shows error messages via <smart-input-error> when the control is touched and invalid.


Usage

<smart-input [options]="options" />

Components

InputComponent (<smart-input>)

Main wrapper component. Resolves fieldOptions from @Field() decorator metadata on the model, then selects a sub-component from baseMap (or extendMap override) based on fieldOptions.type and renders it via NgComponentOutlet. Also renders an inline info button when fieldOptions.info is set and a loader while control.status === 'PENDING'. Below the input, renders <smart-input-error> when control.errors are present and the control has been touched.

InputBaseComponent (abstract)

Abstract base directive for all input sub-components. Provides:

  • options: InputSignal<InputOptions<T>> — field configuration (control, fieldKey, model, treeLevel, possibilities, component)
  • fieldOptions: InputSignal<IFieldOptions | undefined> — resolved field decorator metadata
  • cssClass: InputSignal<string> (alias class) — external CSS classes
  • internalOptions, control, required, possibilities — populated via effect when options change
  • afterSetOptionsHandler() — hook invoked after options resolve, used by sub-components for validator/possibilities setup
  • formControl, formControlArray, formControlGroup — typed accessors for control

InputPossibilitiesBaseComponent (abstract)

Extends InputBaseComponent for sub-components that need list possibilities (radio, check). Injects MODEL_POSSIBILITIES_PROVIDER and auto-refreshes possibilities on parent control changes.

Default Sub-Components

FieldTypeSub-componentSelectorDescription
textInputTextComponentsmart-input-textPlain text input
emailInputEmailComponentsmart-input-emailEmail input
passwordInputPasswordComponentsmart-input-passwordPassword input + optional <smart-password-strength>
nipInputNipComponentsmart-input-nipNIP with built-in invalidNip validator
peselInputPeselComponentsmart-input-peselPESEL text input
intInputIntComponentsmart-input-intInteger input (type="number" step="1")
floatInputFloatComponentsmart-input-floatDecimal input (type="number" step="0.01")
currencyInputCurrencyComponentsmart-input-currencyCurrency input (type="number" step="0.01")
phoneNumberInputPhoneNumberComponentsmart-input-phone-numberTelephone input
phoneNumberPlInputPhoneNumberPlComponentsmart-input-phone-number-plTelephone input + built-in length(9) validator
longTextInputLongTextComponentsmart-input-long-textRich text editor (ngx-editor)
dateInputDateComponentsmart-input-dateHTML date input (type="date")
dateWithEditInputDateWithEditComponentsmart-input-date-with-editDigit-by-digit date via <smart-date-edit>
dateRangeInputDateRangeComponentsmart-input-date-rangeDate range via <smart-date-range>
flagInputFlagComponentsmart-input-flagSingle checkbox
radioInputRadioComponentsmart-input-radioRadio group (from possibilities)
checkInputCheckComponentsmart-input-checkCheckbox group (from possibilities)
enumInputEnumComponentsmart-input-enumMulti-select checkboxes from enum
colorInputColorComponentsmart-input-colorColor picker (ngx-color-picker)
logoInputLogoComponentsmart-input-logoImage upload with inline preview
addressInputAddressComponentsmart-input-addressNested FormGroup (city/zip/street/building/flat)
objectInputObjectComponentsmart-input-objectNested form via FORM_COMPONENT_TOKEN
arrayInputArrayComponentsmart-input-arrayDynamic array of nested forms
intsInputIntsComponentsmart-input-intsDynamic list of integer inputs
stringsInputStringsComponentsmart-input-stringsDynamic list of string inputs
fileInputFileComponentsmart-input-fileGeneric file chooser
attachmentInputAttachmentComponentsmart-input-attachmentFile upload (any type) with download/delete
imageInputImageComponentsmart-input-imageImage upload with preview
pdfInputPdfComponentsmart-input-pdfPDF upload with show/delete
videoInputVideoComponentsmart-input-videoVideo upload with play/delete

API

InputComponent Inputs

InputTypeDefaultDescription
optionsInputSignal<InputOptions<T> | undefined>optionalField configuration

InputOptions

Notes:

  • fieldKey ending with Confirm is stripped — confirm variants share the original field's @Field() metadata.
  • mode === 'create' or 'update' merges the matching @Field({ create: … }) / @Field({ update: … }) override into the resolved fieldOptions.
  • If options.component is provided, it overrides everything (both baseMap and token).

INPUT_FIELD_COMPONENTS_TOKEN

InjectionToken<Partial<Record<FieldTypeDef, Type<InputBaseComponent<any>>>>> — allows substituting a sub-component for any field type.

Maps are merged ({ ...baseMap, ...extendMap }), so only selected types need to be overridden.

Preline field presets

Each field type ships a Preline-styled preset (Input<Field>PresetComponent, selector smart-input-<field>-preset) alongside the default Tailwind-UI-styled field component. The presets live in <field>/preset/ and all translate the Preline look to smart:-prefixed vanilla Tailwind with explicit dark: variants. Apply them by providing the ready-made map INPUT_PRESET_FIELD_COMPONENTS for the token (it covers all field types except the standalone validation-message preset):

INPUT_PRESET_FIELD_COMPONENTS maps every FieldType that ships an input component (dateTime has no input component, so it has no map entry). The field-type presets and their selectors mirror the default components in the table above (smart-input-<field>-preset), including the most recently added ones:

FieldTypePreset componentSelectorLook
colorInputColorPresetComponentsmart-input-color-presetNative color input in a rounded frame + swatch + hex
objectInputObjectPresetComponentsmart-input-object-presetStyled card frame around the nested form (unchanged nesting)
addressInputAddressPresetComponentsmart-input-address-presetResponsive grid of Preline text sub-inputs (street/no/zip/city)
flagInputFlagPresetComponentsmart-input-flag-presetPreline checkbox (blue checked state) + inline label
pdfInputPdfPresetComponentsmart-input-pdf-presetDashed drop-zone (accept .pdf) + show/delete preview
videoInputVideoPresetComponentsmart-input-video-presetDashed drop-zone (accept .mp4) + play/delete + <video>
arrayInputArrayPresetComponentsmart-input-array-presetItem cards (space-y-2), outline add / ghost-red remove

Notes:

  • Interactive Preline widgets (enum select, password strength meter, date pickers, file drop-zone) are driven by Angular signals — the Preline JS plugins are not required.
  • File-based presets (pdf, video, plus file/image/attachment) keep the hidden #inputObj file input and reuse InputFileBaseComponent's upload/button wiring; the drop-zone and drag-and-drop are added on top.
  • object reuses FORM_COMPONENT_TOKEN and the NgComponentOutlet/DynamicIoDirective nesting of the default component — only the surrounding frame is styled, the nested rendering is untouched.
  • array extends the concrete InputArrayComponent (add/remove/CVA logic untouched); each item is a card, the empty state renders an em dash, and both add and remove buttons hide when fieldOptions().possibilities.static is true.
  • InputErrorPresetComponent (smart-input-error-preset) is a Preline-styled validation-message variant. Because InputComponent hard-codes <smart-input-error> (no token for it), use this preset via its selector directly — it is not part of INPUT_PRESET_FIELD_COMPONENTS, and wiring it into the wrapper is out of scope.

Extending the base class

import {
  ChangeDetectionStrategy,
  Component,
  computed,
  ViewEncapsulation,
} from '@angular/core';
import {
  ReactiveFormsModule,
  UntypedFormControl,
  Validators,
} from '@angular/forms';

import {
  INPUT_FIELD_COMPONENTS_TOKEN,
  InputBaseComponent,
  InputComponent,
  InputOptions,
  ModelLabelPipe,
} from '@smartsoft001/angular';
import { Field, FieldType, Model } from '@smartsoft001/models';

@Model({ titleKey: 'nickname' })
export class DocsProfile {
  @Field({ type: FieldType.text })
  nickname = '';
}

@Component({
  selector: 'docs-custom-input-text',
  template: `
    @if (control) {
      <label class="docs-input__label">
        {{
          control?.parent?.value
            | smartModelLabel
              : internalOptions.fieldKey
              : internalOptions?.model?.constructor
        }}
        @if (required) {
          <span class="docs-input__required">*</span>
        }
      </label>
      <input
        type="text"
        [class]="fieldClasses()"
        [formControl]="formControl"
        [attr.autofocus]="fieldOptions()?.focused ? true : null"
      />
    }
  `,
  imports: [ReactiveFormsModule, ModelLabelPipe],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomInputTextComponent<T> extends InputBaseComponent<T> {
  // InputComponent forwards the external class under its aliased name, so the
  // inherited `cssClass` input is used as is - do not redeclare it here.
  fieldClasses = computed(() => {
    const classes = ['docs-input__field'];
    const extra = this.cssClass();
    if (extra) classes.push(extra);
    return classes.join(' ');
  });

  // `control`, `formControl`, `required`, `internalOptions` and
  // `fieldOptions()` are all set up by InputBaseComponent from the options
  // that `<smart-input>` forwards.
}

@Component({
  selector: 'docs-input-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [InputComponent],
  // Inputs are swapped per field type: every FieldType.text field below this
  // component now renders CustomInputTextComponent instead of the built-in
  // one. A single field can also be overridden with `options.component`.
  providers: [
    {
      provide: INPUT_FIELD_COMPONENTS_TOKEN,
      useValue: { [FieldType.text]: CustomInputTextComponent },
    },
  ],
  template: `<smart-input [options]="options" />`,
})
export class InputCustomExampleComponent {
  control = new UntypedFormControl('', Validators.required);

  // `<smart-input>` reads the @Field metadata of `model[fieldKey]` to decide
  // which field component to render, so the model drives the dispatch.
  options: InputOptions<DocsProfile> = {
    control: this.control,
    fieldKey: 'nickname',
    model: new DocsProfile(),
    treeLevel: 0,
  };
}

Source

The component lives in packages/shared/angular/src/lib/components/input and is documented for Claude Code by the angular-components-input skill.