import { AbstractControl, AbstractControlOptions, AsyncValidatorFn, FormControl, FormGroup as NgFormGroup, ValidatorFn } from '@angular/forms'; import { Observable } from 'rxjs'; import { AbstractControlWrapper } from './abstract-control-wrapper'; import { AbstractFormArray } from './form-array'; type ValidatorOrOptsArg = ValidatorFn | ValidatorFn[] | AbstractControlOptions | null; type AsyncValidatorArg = AsyncValidatorFn | AsyncValidatorFn[] | null; /** * The `Form` class is a comprehensive utility for managing form controls and their states. * It provides methods to add, remove, attach, and detach controls, as well as to enable or disable them. * The class also offers observables to track the state of the form and its controls. * * * In general, we have two main states of Control: * - `registered`: control is known to the form, but may be not attached to the Angular FormGroup * - `attached`: control is attached to the Angular FormGroup * * So this way we have a Control's lifecycle like below: * - creation * - registration * - attachment * * With optional phases of: * - detachment * - unregistration * * Once control is detached it can be attached again without knowing its' name, because it's still registered. * * We have shortcuts performing more than one operation, which will be mostly used in common scenarios i.e.: * * `createControl()` = creation + register() + attach(): * ```ts * public readonly name: FormControl = this.createControl('name', ''); * ``` * * - `addControl()` = register() + attach() * * ```ts * public readonly age: FormControl = this.addControl('age', new FormControl()); * ``` * * But, if needed, we can perform each operation separately. I.e. we can register a control without attaching it to the form: * ```ts * public readonly document: FormControl = this.registerControl('document', nonNullControl('')); * ``` * * For adding subforms and form arrays, we have dedicated methods: * - `addForm()` * - `addArray()` * * This class provides also `setChildEnabled()` and `setChildDisabled()` methods, * which should be used to enable/disable controls with attaching/detaching * them at the same time without a risk of accidental enabling of them while * enabling the whole form. * * @example * ```ts * export class PersonForm extends Form { * public readonly name: FormControl = this.createControl('name', '', Validators.required); * public readonly age: FormControl = this.createControl('age', 0, Validators.min(0)); * public readonly document: FormControl = this.createControl('document', ''); * public readonly address: AddressForm = this.addForm('address', new AddressForm()); * public readonly books: FormArray = this.addArray('books', new FormArray()); * * public readonly saveWorker: Worker = this.observer.add(new Worker()); * public readonly schema$: State = this.observer.add(new State(null)); * public readonly isSubmitDisabled$: Snapshot = this.observer.add( * Snapshot.from(false, Rx.atLeastOneIsTrue([this.isPending$, this.isDisabled$])) * ); * * public constructor() { * super(); * this.listen(this.saveWorker.isWorking$, this.setDisabled); * * this.listen(FormTools.getValue$(this.age).pipe(map((age) => age >= 18)), (hasDocument) => { * this.setChildEnabled(this.document)(hasDocument); * FormTools.setValidators(this.document, hasDocument ? [Validators.required, Validators.minLength(3)] : null); * }); * * this.listen(this.schema$, (schema) => { * this.name.setValue(schema?.name ?? ''); * this.age.setValue(schema?.age ?? 0); * this.document.setValue(schema?.document ?? ''); * this.address.schema.set(schema?.address ?? null); * this.books.updateItemsByValues({ * values: schema?.books ?? [], * hasValue: (form, value) => form.schema.get()?.id === value.id, * create: (value) => new BookForm(value), * update: (form, value) => form.schema.set(value), * destroy: (form) => form.destroy(), * }); * }); * } * * public toSchema(): PersonSchema { * return { * id: this.schema$.get()?.id, * name: this.name.value, * age: this.age.value, * document: this.document.value, * address: this.address.toSchema(), * books: this.books.items.map((form) => form.toSchema()), * }; * } * * public addBook(): void { * this.books.push(new BookForm()); * } * * public removeBook(book: BookForm): void { * this.books.removeItem(book); * } * } * ``` */ export declare class Form extends AbstractControlWrapper { private readonly _registeredControls; private readonly _registeredControlsReversed; /** * An observable that emits when the collection of registered controls in the form is changed. */ readonly controls$: Observable>; constructor(opts?: AbstractControlOptions); /** * Destroys the form and all its controls. */ destroy(): void; /** * Returns all the controls registered in the form, including those detached from the Angular FormGroup. */ getAllControls(): Record; getControlByName(name: string): AbstractControl; getNameByControl(control: AbstractControl): string; /** * Adds a control to the form and attaches it to the underlying Angular FormGroup. */ addControl(name: string, control: TControl): TControl; /** * Creates a new FormControl and adds it to the form (and attaches to Angular FormGroup). */ createControl(name: string, value: AbstractControl | T, validatorOrOpts?: ValidatorOrOptsArg, asyncValidator?: AsyncValidatorArg): FormControl; /** * Adds a subform extending Form class to the form and attaches it's native * NgFormGroup to the current Angular FormGroup. */ addForm(name: string, form: TForm): TForm; /** * Adds a FormArray or FormControlArray class to the form and attaches it's native * NgFormArray to the current Angular FormGroup. */ addArray>(name: string, form: TForm): TForm; /** * Checks if the control is registered to the form (this does not mean it is attached to the Angular FormGroup). */ isRegistered(control: AbstractControl): boolean; /** * Registers a control in the Form, so it will be associated with its' name. */ register>(name: string, control: TControl): TControl; /** * Unregisters a control from the Form, so it won't be known for this Form anymore. */ unregister>(name: string, control: TControl): TControl; /** * Checks if the control is attached to the underlying Angular FormGroup. */ isAttached(control: AbstractControl): boolean; /** * Attaches a control to the Angular FormGroup. */ attach(control: AbstractControl): this; /** * Detaches a control from the Angular FormGroup (but keeps it registered in the form, so it can be * attached later again). Detaching control means it won't be included in Angular's validation process. */ detach(control: AbstractControl): this; readonly setEnabled: (value?: boolean) => void; readonly setDisabled: (value?: boolean) => void; /** * It will enable & attach (or disable & dettach) a child control based on the given value. */ readonly setChildEnabled: >(control: TControl | AbstractControlWrapper) => (value: boolean) => void; /** * It will disable & dettach (or enable & attach) a child control based on the given value. */ readonly setChildDisabled: >(control: TControl | AbstractControlWrapper) => (value: boolean) => void; } export {};