Examples Complex Form

Complex Form

Complex form example

Sender

Name is required

Country is required

City is required

Postal code is required

Street is required

Recipient

Name is required

Country is required

City is required

Postal code is required

Street is required

Select the type of shipment:
Packages

Form Inspector

Field Tree

(root) valid: false | invalid: true | pending: false
value: {
  "shipmentType": "package",
  "sender": {
    "name": "",
    "address": {
      "country": "",
      "city": "",
      "postalCode": "",
      "street": ""
    }
  },
  "recipient": {
    "name": "",
    "address": {
      "country": "",
      "city": "",
      "postalCode": "",
      "street": ""
    }
  },
  "packages": [],
  "documents": []
}
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (0)
[]
errorSummary: (10)
[
  {
    "kind": "required",
    "message": "Name is required"
  },
  {
    "kind": "required",
    "message": "Country is required"
  },
  {
    "kind": "required",
    "message": "City is required"
  },
  {
    "kind": "required",
    "message": "Postal code is required"
  },
  {
    "kind": "required",
    "message": "Street is required"
  },
  {
    "kind": "required",
    "message": "Name is required"
  },
  {
    "kind": "required",
    "message": "Country is required"
  },
  {
    "kind": "required",
    "message": "City is required"
  },
  {
    "kind": "required",
    "message": "Postal code is required"
  },
  {
    "kind": "required",
    "message": "Street is required"
  }
]
meta: {
  "pattern": [],
  "required": false
}
            

Children

shipmentType valid: true | invalid: false | pending: false
value: "package"
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (0)
[]
errorSummary: (0)
[]
meta: {
  "pattern": [],
  "required": false
}
            

Children

— none —

sender valid: false | invalid: true | pending: false
value: {
  "name": "",
  "address": {
    "country": "",
    "city": "",
    "postalCode": "",
    "street": ""
  }
}
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (0)
[]
errorSummary: (5)
[
  {
    "kind": "required",
    "message": "Name is required"
  },
  {
    "kind": "required",
    "message": "Country is required"
  },
  {
    "kind": "required",
    "message": "City is required"
  },
  {
    "kind": "required",
    "message": "Postal code is required"
  },
  {
    "kind": "required",
    "message": "Street is required"
  }
]
meta: {
  "pattern": [],
  "required": false
}
            

Children

name valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "Name is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "Name is required"
  }
]
meta: {
  "pattern": [],
  "required": true
}
            

Children

— none —

address valid: false | invalid: true | pending: false
value: {
  "country": "",
  "city": "",
  "postalCode": "",
  "street": ""
}
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (0)
[]
errorSummary: (4)
[
  {
    "kind": "required",
    "message": "Country is required"
  },
  {
    "kind": "required",
    "message": "City is required"
  },
  {
    "kind": "required",
    "message": "Postal code is required"
  },
  {
    "kind": "required",
    "message": "Street is required"
  }
]
meta: {
  "pattern": [],
  "required": false
}
            

Children

country valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "Country is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "Country is required"
  }
]
meta: {
  "pattern": [],
  "required": true
}
            

Children

— none —

city valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "City is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "City is required"
  }
]
meta: {
  "pattern": [],
  "required": true
}
            

Children

— none —

postalCode valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "Postal code is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "Postal code is required"
  }
]
meta: {
  "minLen": 5,
  "pattern": [],
  "required": true
}
            

Children

— none —

street valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "Street is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "Street is required"
  }
]
meta: {
  "pattern": [],
  "required": true
}
            

Children

— none —

recipient valid: false | invalid: true | pending: false
value: {
  "name": "",
  "address": {
    "country": "",
    "city": "",
    "postalCode": "",
    "street": ""
  }
}
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (0)
[]
errorSummary: (5)
[
  {
    "kind": "required",
    "message": "Name is required"
  },
  {
    "kind": "required",
    "message": "Country is required"
  },
  {
    "kind": "required",
    "message": "City is required"
  },
  {
    "kind": "required",
    "message": "Postal code is required"
  },
  {
    "kind": "required",
    "message": "Street is required"
  }
]
meta: {
  "pattern": [],
  "required": false
}
            

Children

name valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "Name is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "Name is required"
  }
]
meta: {
  "pattern": [],
  "required": true
}
            

Children

— none —

address valid: false | invalid: true | pending: false
value: {
  "country": "",
  "city": "",
  "postalCode": "",
  "street": ""
}
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (0)
[]
errorSummary: (4)
[
  {
    "kind": "required",
    "message": "Country is required"
  },
  {
    "kind": "required",
    "message": "City is required"
  },
  {
    "kind": "required",
    "message": "Postal code is required"
  },
  {
    "kind": "required",
    "message": "Street is required"
  }
]
meta: {
  "pattern": [],
  "required": false
}
            

Children

country valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "Country is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "Country is required"
  }
]
meta: {
  "pattern": [],
  "required": true
}
            

Children

— none —

city valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "City is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "City is required"
  }
]
meta: {
  "pattern": [],
  "required": true
}
            

Children

— none —

postalCode valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "Postal code is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "Postal code is required"
  }
]
meta: {
  "minLen": 5,
  "pattern": [],
  "required": true
}
            

Children

— none —

street valid: false | invalid: true | pending: false
value: ""
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (1)
[
  {
    "kind": "required",
    "message": "Street is required"
  }
]
errorSummary: (1)
[
  {
    "kind": "required",
    "message": "Street is required"
  }
]
meta: {
  "pattern": [],
  "required": true
}
            

Children

— none —

packages valid: true | invalid: false | pending: false
value: []
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (0)
[]
errorSummary: (0)
[]
meta: {
  "pattern": [],
  "required": false
}
            

Children

— none —

documents valid: true | invalid: false | pending: false
value: []
touched: false
dirty: false
hidden: false
readonly: false
disabled: false
disabledReasons: (0)
[]
errors: (0)
[]
errorSummary: (0)
[]
meta: {
  "pattern": [],
  "required": false
}
            

Children

— none —

TypeScript
HTML
import { ChangeDetectionStrategy, Component, effect, signal } from '@angular/core';
import {
  apply,
  applyEach,
  applyWhen,
  createMetadataKey,
  Field,
  form,
  max,
  metadata,
  min,
  minLength,
  required,
  schema,
} from '@angular/forms/signals';
import { FormInspectorComponent } from '../../../ui/form-inspector.ts/form-inspector';
import { DemoLayout } from '../../../ui/demo-layout/demo-layout';

interface ShippingFormModel {
  shipmentType: 'document' | 'package';
  sender: AddressContact;
  recipient: AddressContact;
  packages: Package[];
  documents: Document[];
}

export type Package = {
  description: string;
  weight: number;
  dimensions: {
    length: number;
    width: number;
    height: number;
  };
};

export type Document = {
  description: string;
  weight: number;
};

export type AddressContact = {
  name: string;
  address: {
    country: string;
    city: string;
    postalCode: string;
    street: string;
  };
};

const addressContactSchema = schema<AddressContact>((addressPath) => {
  required(addressPath.name, { message: 'Name is required' });
  required(addressPath.address.country, { message: 'Country is required' });
  required(addressPath.address.city, { message: 'City is required' });
  required(addressPath.address.postalCode, { message: 'Postal code is required' });
  minLength(addressPath.address.postalCode, 5, {
    message: 'Postal code must be at least 5 characters',
  });
  required(addressPath.address.street, { message: 'Street is required' });
});

const packageSchema = schema<Package>((packagePath) => {
  required(packagePath.description, { message: 'Description is required' });
  required(packagePath.weight, { message: 'Weight is required' });
  min(packagePath.weight, 0.1, { message: 'Weight must be at least 0.1 kg' });
  max(packagePath.weight, 30, { message: 'Weight must be maximum 30 kg' });
  required(packagePath.dimensions, { message: 'Size is required' });
});

const documentSchema = schema<Document>((documentPath) => {
  required(documentPath.weight, { message: 'Weight is required' });
  min(documentPath.weight, 0.1, { message: 'Weight must be at least 0.1 kg' });
  max(documentPath.weight, 2, { message: 'Weight must be maximum 2 kg' });
});

@Component({
  selector: 'complex-form',
  templateUrl: './complex-form.html',
  imports: [Field, FormInspectorComponent, DemoLayout],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ComplexForm {
  protected readonly SAME_COUNTRY = createMetadataKey<boolean>();
  protected readonly shippingForm = form(
    signal<ShippingFormModel>({
      shipmentType: 'package',
      sender: {
        name: '',
        address: { country: '', city: '', postalCode: '', street: '' },
      },
      recipient: {
        name: '',
        address: { country: '', city: '', postalCode: '', street: '' },
      },
      packages: [],
      documents: [],
    }),
    schema<ShippingFormModel>((shippingPath) => {
      apply(shippingPath.sender, addressContactSchema);
      apply(shippingPath.recipient, addressContactSchema);
      metadata(shippingPath, this.SAME_COUNTRY, ({ valueOf }) => {
        const senderCountry = valueOf(shippingPath.sender.address.country);
        const recipientCountry = valueOf(shippingPath.recipient.address.country);
        return senderCountry === recipientCountry && senderCountry !== '';
      });
      applyWhen(
        shippingPath.packages,
        () => this.shippingForm.shipmentType().value() === 'package',
        (packages) => {
          applyEach(packages, packageSchema);
        },
      );
      applyWhen(
        shippingPath.documents,
        () => this.shippingForm.shipmentType().value() === 'document',
        (documents) => {
          applyEach(documents, documentSchema);
        },
      );
    }),
  );

  addPackage() {
    this.shippingForm
      .packages()
      .value.update((packages) => [
        ...packages,
        { description: '', weight: 0, dimensions: { length: 0, width: 0, height: 0 } },
      ]);
  }

  addDocument() {
    this.shippingForm
      .documents()
      .value.update((documents) => [...documents, { description: '', weight: 0 }]);
  }
}