import type { FormControlStatus } from './abstract-control';
import type { AsyncValidatorFn, ValidatorFn } from './validators';
import { AbstractControl, VALID } from './abstract-control';
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type GetFormControlPropertyFromArray<T, A> = Mutable<A> extends [infer K, ...infer R]
? K extends keyof T
? GetFormControlPropertyFromArray<T[K], R>
: any
: T;
type GetFormControlProperty<T, S> = S extends `${infer K}.${infer R}`
? K extends keyof T
? GetFormControlProperty<T[K], R>
: any
: S extends keyof T
? T[S]
: any;
type GetFormGroupT<C extends FormGroup<any>> = C extends FormGroup<infer T> ? T : unknown;
type GetFormGroupValue<T extends { [K in keyof T]: AbstractControl }> = {
[K in keyof T]?: T[K] extends FormGroup ? GetFormGroupValue<GetFormGroupT<T[K]>> : T[K]['value'];
};
function find(control: AbstractControl, path: string[] | string, delimiter: string) {
if (path == null) return null;
if (!Array.isArray(path)) {
path = path.split(delimiter);
}
if (Array.isArray(path) && path.length === 0) return null;
let controlToFind: AbstractControl | null = control;
path.forEach((name) => {
if (controlToFind instanceof FormGroup) {
controlToFind = name in controlToFind.controls ? controlToFind.controls[name] : null;
} else {
controlToFind = null;
}
});
return controlToFind;
}
export class FormGroup<T extends { [K in keyof T]: AbstractControl } = any> extends AbstractControl<{ [K in keyof T]: T[K]['value'] }> {
protected _value!: { [K in keyof T]: T[K]['value'] };
protected _status: FormControlStatus = VALID;
constructor(
public controls: T,
validators?: ValidatorFn | ValidatorFn[] | null,
asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null
) {
super(validators ?? null, asyncValidator ?? null);
this._setUpControls();
this.updateValueAndValidity(true);
}
get<S extends string>(path: S): AbstractControl<GetFormControlProperty<{ [K in keyof T]: T[K]['value'] }, S>>;
get<S extends ArrayLike<string>>(path: S): AbstractControl<GetFormControlPropertyFromArray<{ [K in keyof T]: T[K]['value'] }, S>>;
get(path: string[] | string): AbstractControl | null {
return find(this, path, '.');
}
getError(errorCode: string, path?: string[] | string): any {
const control = path ? this.get(path) : this;
return control && control.errors ? control.errors[errorCode] : null;
}
hasError(errorCode: string, path?: string[] | string): boolean {
return !!this.getError(errorCode, path);
}
addControl<K extends keyof T>(name: K, control: T[K]): void;
addControl(name: string, control: AbstractControl): void;
addControl(name: string, control: AbstractControl): void {
if (!(name in this.controls)) {
this.controls[name] = control;
control.setParent(this);
}
this.updateValueAndValidity();
}
removeControl<K extends keyof T>(name: K): void;
removeControl(name: string): void;
removeControl(name: string): void {
delete this.controls[name];
this.updateValueAndValidity();
}
setControl<K extends keyof T>(name: K, control: T[K]): void;
setControl(name: string, control: AbstractControl): void;
setControl(name: string, control: AbstractControl): void {
delete this.controls[name];
this.addControl(name, control);
this.updateValueAndValidity();
}
contains<K extends keyof T>(controlName: K): boolean;
contains(controlName: string): boolean;
contains(controlName: string): boolean {
return controlName in this.controls;
}
override setValue(value: { [K in keyof T]: T[K]['value'] } & { [K: string]: any }, onlySelf?: boolean): void {
this._checkAllValuesPresent(value);
Object.keys(value).forEach((name) => {
this._throwIfControlMissing(name);
this.controls[name].setValue(value[name], true);
});
this.updateValueAndValidity(onlySelf);
}
override patchValue(value: GetFormGroupValue<T> & { [K: string]: any }, onlySelf?: boolean): void {
Object.keys(value).forEach((name) => {
if (this.controls[name]) {
this.controls[name].patchValue(value[name], true);
}
});
this.updateValueAndValidity(onlySelf);
}
override reset(value: GetFormGroupValue<T> & { [K: string]: any } = {}, onlySelf?: boolean): void {
this._forEachChild((control, name) => {
control.reset(value[name], true);
});
this._updatePristine(onlySelf);
this.updateValueAndValidity(onlySelf);
}
protected override _updateValue(): void {
this._value = this._reduceValue();
}
protected override _forEachChild(cb: (v: any, k: string) => void): void {
Object.keys(this.controls).forEach((key) => {
const control = this.controls[key];
control && cb(control, key);
});
}
protected override _anyControls(condition: (c: AbstractControl) => boolean): boolean {
for (const controlName of Object.keys(this.controls)) {
const control = this.controls[controlName];
if (this.contains(controlName) && condition(control)) {
return true;
}
}
return false;
}
protected override _allControlsDisabled(): boolean {
for (const controlName of Object.keys(this.controls)) {
if (this.controls[controlName].enabled) {
return false;
}
}
return Object.keys(this.controls).length > 0 || this.disabled;
}
private _setUpControls(): void {
this._forEachChild((control) => {
control.setParent(this);
});
}
private _reduceValue(): any {
return this._reduceChildren({}, (acc: { [k: string]: any }, control, name): any => {
acc[name] = control.value;
return acc;
});
}
private _reduceChildren<T>(initValue: T, fn: (acc: T, control: AbstractControl, name: string) => T): T {
let res = initValue;
this._forEachChild((control, name) => {
res = fn(res, control, name);
});
return res;
}
private _checkAllValuesPresent(value: any): void {
this._forEachChild((control, name) => {
if (value[name] === undefined) {
throw new Error(`Must supply a value for form control with name: '${name}'.`);
}
});
}
private _throwIfControlMissing(name: string): void {
if (!Object.keys(this.controls).length) {
throw new Error(`
There are no form controls registered with this group yet. If you're using ngModel,
you may want to check next tick (e.g. use setTimeout).
`);
}
if (!this.controls[name]) {
throw new Error(`Cannot find form control with name: ${name}.`);
}
}
}