Repository: IduxFE/components
Pull request #139 (2021-01-15 04:53:00)

feat(cdk:forms): add formGroup and formArray
* feat(cdk:forms): add test

re #115

## PR Checklist

Please check if your PR fulfills the following requirements:

- [x] The commit message follows [our guidelines](https://github.com/IduxFE/components/blob/main/docs/contributing.zh.md#commit)
- [x] Tests for the changes have been added/updated or not needed
- [x] Docs and demo have been added/updated or not needed

## PR Type

What kind of change does this PR introduce?

- [ ] Bugfix
- [x] Feature
- [ ] Component style update
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Application (the showcase website) / infrastructure changes
- [ ] Other... Please describe:

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

## What is the new behavior?

## Other information


codecov[bot]: # [Codecov](https://codecov.io/gh/IduxFE/components/pull/139?src=pr&el=h1) Report
> Merging [#139](https://codecov.io/gh/IduxFE/components/pull/139?src=pr&el=desc) (0b3cb96) into [main](https://codecov.io/gh/IduxFE/components/commit/39b773cba37cdffa64c9e22b6b318a2674e06415?el=desc) (39b773c) will **increase** coverage by `1.01%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/IduxFE/components/pull/139/graphs/tree.svg?width=650&height=150&src=pr&token=PGAUXP06V3)](https://codecov.io/gh/IduxFE/components/pull/139?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##             main     #139      +/-   ##
==========================================
+ Coverage   96.24%   97.26%   +1.01%     
==========================================
  Files          50       53       +3     
  Lines         773      987     +214     
  Branches      141      169      +28     
==========================================
+ Hits          744      960     +216     
+ Misses         11       10       -1     
+ Partials       18       17       -1     
```


| [Impacted Files](https://codecov.io/gh/IduxFE/components/pull/139?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packages/cdk/forms/src/controls/abstractControl.ts](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL2Zvcm1zL3NyYy9jb250cm9scy9hYnN0cmFjdENvbnRyb2wudHM=) | `100.00% <100.00%> (ø)` | |
| [packages/cdk/forms/src/controls/formArray.ts](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL2Zvcm1zL3NyYy9jb250cm9scy9mb3JtQXJyYXkudHM=) | `100.00% <100.00%> (ø)` | |
| [packages/cdk/forms/src/controls/formControl.ts](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL2Zvcm1zL3NyYy9jb250cm9scy9mb3JtQ29udHJvbC50cw==) | `100.00% <100.00%> (ø)` | |
| [packages/cdk/forms/src/controls/formGroup.ts](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL2Zvcm1zL3NyYy9jb250cm9scy9mb3JtR3JvdXAudHM=) | `100.00% <100.00%> (ø)` | |
| [packages/cdk/forms/src/typeof.ts](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL2Zvcm1zL3NyYy90eXBlb2YudHM=) | `100.00% <100.00%> (ø)` | |
| [packages/cdk/forms/src/useForms.ts](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL2Zvcm1zL3NyYy91c2VGb3Jtcy50cw==) | `100.00% <100.00%> (ø)` | |
| [packages/cdk/forms/src/validators.ts](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL2Zvcm1zL3NyYy92YWxpZGF0b3JzLnRz) | `100.00% <100.00%> (ø)` | |
| [packages/cdk/utils/typeof.ts](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL3V0aWxzL3R5cGVvZi50cw==) | `100.00% <100.00%> (ø)` | |
| ... and [4 more](https://codecov.io/gh/IduxFE/components/pull/139/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/IduxFE/components/pull/139?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/IduxFE/components/pull/139?src=pr&el=footer). Last update [39b773c...0b3cb96](https://codecov.io/gh/IduxFE/components/pull/139?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/packages/cdk/forms/index.ts b/packages/cdk/forms/index.ts
index c38dc75e..a8d8322d 100644
--- a/packages/cdk/forms/index.ts
+++ b/packages/cdk/forms/index.ts
@@ -1,4 +1,7 @@
-export * from './src/typeof'
+export * from './src/controls/abstractControl'
+export * from './src/controls/formArray'
+export * from './src/controls/formControl'
+export * from './src/controls/formGroup'
 export * from './src/types'
-export * from './src/useFormControl'
+export * from './src/useForms'
 export * from './src/validators'
diff --git a/packages/cdk/forms/src/constant.ts b/packages/cdk/forms/src/constant.ts
deleted file mode 100644
index 9eb9c44a..00000000
--- a/packages/cdk/forms/src/constant.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-// PascalCase is being used as Breakpoints is used like an enum.
-export const ModelType = {
-  Control: '__control',
-}
diff --git a/packages/cdk/forms/src/controls/abstractControl.ts b/packages/cdk/forms/src/controls/abstractControl.ts
new file mode 100644
index 00000000..596274b5
--- /dev/null
+++ b/packages/cdk/forms/src/controls/abstractControl.ts
@@ -0,0 +1,291 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import type { ComputedRef, DeepReadonly, Ref } from 'vue'
+import type {
+  AsyncValidatorFn,
+  ErrorMessages,
+  ValidationErrors,
+  TriggerType,
+  ValidationError,
+  ValidatorFn,
+  ValidatorOptions,
+  ValidationStatus,
+} from '../types'
+import type { FormGroup } from './formGroup'
+import type { FormArray } from './formArray'
+
+import { computed, readonly, ref } from 'vue'
+import { hasOwnProperty, isArray, isNil, isObject } from '@idux/cdk/utils'
+import { Validators } from '../validators'
+import { isFormArray, isFormGroup } from '../typeof'
+
+export abstract class AbstractControl<T = any> {
+  /**
+   * The ref value for the control.
+   */
+  readonly modelRef!: any
+
+  /**
+   * The validation status of the control, there are three possible validation status values:
+   * * **valid**: This control has passed all validation checks.
+   * * **invalid**: This control has failed at least one validation check.
+   * * **validating**: This control is in the midst of conducting a validation check.
+   */
+  readonly status: DeepReadonly<Ref<ValidationStatus>>
+
+  /**
+   * An object containing any errors generated by failing validation, or null if there are no errors.
+   */
+  readonly errors: DeepReadonly<Ref<ValidationErrors | null>>
+
+  /**
+   * A control is valid when its `status` is `valid`.
+   */
+  readonly valid: ComputedRef<boolean>
+
+  /**
+   * A control is invalid when its `status` is `invalid`.
+   */
+  readonly invalid: ComputedRef<boolean>
+
+  /**
+   * A control is validating when its `status` is `validating`.
+   */
+  readonly validating: ComputedRef<boolean>
+
+  /**
+   * A control is marked `blurred` once the user has triggered a `blur` event on it.
+   */
+  readonly blurred: ComputedRef<boolean>
+
+  /**
+   * A control is `unblurred` if the user has not yet triggered a `blur` event on it.
+   */
+  readonly unblurred: ComputedRef<boolean>
+
+  /**
+   * The parent control.
+   */
+  get parent(): FormGroup<T> | FormArray<T[]> | null {
+    return this._parent
+  }
+
+  /**
+   * Retrieves the top-level ancestor of this control.
+   */
+  get root(): AbstractControl<T> {
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    let x = this as AbstractControl<T>
+
+    while (x.parent) {
+      x = x.parent as AbstractControl<T>
+    }
+
+    return x
+  }
+
+  /**
+   * Reports the trigger validate of the `AbstractControl`.
+   * Possible values: `'change'` | `'blur'` | `'submit'`
+   * Default value: `'change'`
+   */
+  get trigger(): TriggerType {
+    return this._trigger ?? this._parent?.trigger ?? 'change'
+  }
+
+  protected _status = ref<ValidationStatus>('valid')
+  protected _errors = ref<ValidationErrors | null>(null)
+  protected _blurred = ref(false)
+
+  protected _validators: ValidatorFn | null = null
+  protected _asyncValidators: AsyncValidatorFn | null = null
+
+  private _parent: FormGroup<T> | FormArray<T[]> | null = null
+  private _trigger?: TriggerType
+
+  constructor(
+    validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
+    asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+  ) {
+    this.status = readonly(this._status)
+    this.errors = readonly(this._errors)
+    this.valid = computed(() => this.status.value === 'valid')
+    this.invalid = computed(() => this.status.value === 'invalid')
+    this.validating = computed(() => this.status.value === 'validating')
+    this.blurred = computed(() => this._blurred.value)
+    this.unblurred = computed(() => !this._blurred.value)
+
+    this._convertOptions(validatorOrOptions, asyncValidator)
+  }
+
+  /**
+   * Resets the control, marking it `unblurred`, and setting the value to initialization value.
+   */
+  abstract reset(): void
+
+  /**
+   * Sets a new value for the control.
+   *
+   * @param value The new value.
+   */
+  abstract setValue(value: any): void
+
+  /**
+   * The aggregate value of the control.
+   */
+  abstract getValue(): any
+
+  /**
+   * Marks the control as `blurred`.
+   */
+  abstract markAsBlurred(): void
+
+  /**
+   * Marks the control as `unblurred`.
+   */
+  abstract markAsUnblurred(): void
+
+  /**
+   * Running validations manually, rather than automatically.
+   */
+  abstract validate(): Promise<ValidationErrors | null>
+
+  /**
+   * Sets the new sync validator for the form control, it overwrites existing sync validators.
+   * If you want to clear all sync validators, you can pass in a null.
+   */
+  setValidator(newValidator: ValidatorFn | ValidatorFn[] | null): void {
+    this._validators = toValidator(newValidator)
+  }
+
+  /**
+   * Sets the new async validator for the form control, it overwrites existing async validators.
+   * If you want to clear all async validators, you can pass in a null.
+   */
+  setAsyncValidator(newAsyncValidator: AsyncValidatorFn | AsyncValidatorFn[] | null): void {
+    this._asyncValidators = toAsyncValidator(newAsyncValidator)
+  }
+
+  /**
+   * Retrieves a child control given the control's name or path.
+   *
+   * @param path A dot-delimited string or array of string/number values that define the path to the
+   * control.
+   */
+  get(path: Array<string | number> | string): AbstractControl<T> | null {
+    if (isNil(path)) {
+      return null
+    }
+    if (!isArray(path)) {
+      path = path.split('.')
+    }
+    if (path.length === 0) {
+      return null
+    }
+
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    let controlToFind: AbstractControl | null = this
+    // Not using Array.reduce here due to a Chrome 80 bug
+    // https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
+    path.forEach((name: string | number) => {
+      if (isFormGroup(controlToFind)) {
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        controlToFind = hasOwnProperty(controlToFind.controls, name as string) ? controlToFind.controls[name]! : null
+      } else if (isFormArray(controlToFind)) {
+        controlToFind = controlToFind.at(<number>name) || null
+      } else {
+        controlToFind = null
+      }
+    })
+    return controlToFind
+  }
+
+  /**
+   * Sets errors on a form control when running validations manually, rather than automatically.
+   */
+  setErrors(errors: ValidationErrors | null): void {
+    this._errors.value = errors
+  }
+
+  /**
+   * @description
+   * Reports error data for the control with the given path.
+   *
+   * @param errorCode The code of the error to check
+   * @param path A list of control names that designates how to move from the current control
+   * to the control that should be queried for errors.
+   */
+  getError(errorCode: keyof ErrorMessages, path?: Array<string | number> | string): ValidationError | null {
+    const control = path ? this.get(path) : this
+    return control?.errors?.value?.[errorCode] || null
+  }
+
+  /**
+   * @description
+   * Reports whether the control with the given path has the error specified.
+   *
+   * @param errorCode The code of the error to check
+   * @param path A list of control names that designates how to move from the current control
+   * to the control that should be queried for errors.
+   *
+   */
+  hasError(errorCode: string, path?: Array<string | number> | string): boolean {
+    return !!this.getError(errorCode, path)
+  }
+
+  /**
+   * @param parent Sets the parent of the control
+   */
+  setParent(parent: FormGroup<T> | FormArray<T[]>): void {
+    this._parent = parent
+  }
+
+  protected async _validate(): Promise<ValidationErrors | null> {
+    const value = this.getValue()
+    let newErrors = this._runValidator(value)
+    if (isNil(newErrors)) {
+      newErrors = await this._runAsyncValidator(value)
+    }
+    this.setErrors(newErrors)
+    return newErrors
+  }
+
+  private _runValidator(value: any): ValidationErrors | null {
+    return this._validators ? this._validators(value, this) : null
+  }
+
+  private _runAsyncValidator(value: any): Promise<ValidationErrors | null> {
+    if (!this._asyncValidators) {
+      return Promise.resolve(null)
+    }
+    this._status.value = 'validating'
+    return this._asyncValidators(value, this)
+  }
+
+  private _convertOptions(
+    validatorOrOptions: ValidatorFn | ValidatorFn[] | ValidatorOptions | null | undefined,
+    asyncValidator: AsyncValidatorFn | AsyncValidatorFn[] | null | undefined,
+  ) {
+    if (isOptions(validatorOrOptions)) {
+      this._trigger = validatorOrOptions.trigger ?? this._trigger
+      this._validators = toValidator(validatorOrOptions.validators)
+      this._asyncValidators = toAsyncValidator(validatorOrOptions.asyncValidators)
+    } else {
+      this._validators = toValidator(validatorOrOptions)
+      this._asyncValidators = toAsyncValidator(asyncValidator)
+    }
+  }
+}
+
+function isOptions(val?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null): val is ValidatorOptions {
+  return isObject(val) && !isArray(val)
+}
+
+function toValidator(validator?: ValidatorFn | ValidatorFn[] | null): ValidatorFn | null {
+  return isArray(validator) ? Validators.compose(validator) : validator || null
+}
+
+function toAsyncValidator(asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): AsyncValidatorFn | null {
+  return isArray(asyncValidator) ? Validators.composeAsync(asyncValidator) : asyncValidator || null
+}
diff --git a/packages/cdk/forms/src/controls/formArray.ts b/packages/cdk/forms/src/controls/formArray.ts
new file mode 100644
index 00000000..3eb7bd59
--- /dev/null
+++ b/packages/cdk/forms/src/controls/formArray.ts
@@ -0,0 +1,206 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import type { Ref, UnwrapRef, WatchStopHandle } from 'vue'
+import type { AsyncValidatorFn, ValidatorFn, ValidatorOptions, ValidationErrors, ValidationStatus } from '../types'
+
+import { ref, watch, watchEffect } from 'vue'
+import { AbstractControl } from './abstractControl'
+
+export class FormArray<T = any[]> extends AbstractControl<T> {
+  /**
+   * The ref value for the form array.
+   */
+  readonly modelRef: Ref<Array<UnwrapRef<T>>>
+
+  /**
+   * Length of the control array.
+   */
+  get length(): number {
+    return this.controls.length
+  }
+
+  private _statusWatchStopHandle: WatchStopHandle | null = null
+  private _blurredWatchStopHandle: WatchStopHandle | null = null
+
+  constructor(
+    public controls: AbstractControl[],
+    validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
+    asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+  ) {
+    super(validatorOrOptions, asyncValidator)
+    controls.forEach(control => control.setParent(this as any))
+    this.modelRef = ref(this._calculateModelRef(controls))
+
+    this._watchValid()
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Get the `AbstractControl` at the given `index` in the array.
+   *
+   * @param index Index in the array to retrieve the control
+   */
+  at(index: number): AbstractControl {
+    return this.controls[index]
+  }
+
+  /**
+   * Insert a new `AbstractControl` at the end of the array.
+   *
+   * @param control Form control to be inserted
+   */
+  push(control: AbstractControl): void {
+    this.controls.push(control)
+    this._registerControl(control)
+    this.modelRef.value = this._calculateModelRef(this.controls)
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Insert a new `AbstractControl` at the given `index` in the array.
+   *
+   * @param index Index in the array to insert the control
+   * @param control Form control to be inserted
+   */
+  insert(index: number, control: AbstractControl): void {
+    this.controls.splice(index, 0, control)
+    this._registerControl(control)
+    this.modelRef.value = this._calculateModelRef(this.controls)
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Remove the control at the given `index` in the array.
+   *
+   * @param index Index in the array to remove the control
+   */
+  removeAt(index: number): void {
+    this.controls.splice(index, 1)
+    this.modelRef.value = this._calculateModelRef(this.controls)
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Replace an existing control.
+   *
+   * @param index Index in the array to replace the control
+   * @param control The `AbstractControl` control to replace the existing control
+   */
+  setControl(index: number, control: AbstractControl): void {
+    this.controls.splice(index, 1, control)
+    this._registerControl(control)
+    this.modelRef.value = this._calculateModelRef(this.controls)
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Resets all controls of the form array.
+   */
+  reset(): void {
+    this.controls.forEach(control => control.reset())
+  }
+
+  /**
+   * Sets a new value for the form array.
+   *
+   * @param value The new value.
+   */
+  setValue(value: Partial<T>[]): void {
+    value.forEach((item, index) => {
+      if (this.at(index)) {
+        this.at(index).setValue(item)
+      }
+    })
+  }
+
+  /**
+   * The aggregate value of the form array.
+   */
+  getValue(): T[] {
+    return this.controls.map(control => control.getValue()) as T[]
+  }
+
+  /**
+   * Marks all controls of the form array as `blurred`.
+   */
+  markAsBlurred(): void {
+    this.controls.forEach(control => control.markAsBlurred())
+  }
+
+  /**
+   * Marks all controls of the form array as `unblurred`.
+   */
+  markAsUnblurred(): void {
+    this.controls.forEach(control => control.markAsUnblurred())
+  }
+
+  /**
+   * Running validations manually, rather than automatically.
+   */
+  async validate(): Promise<ValidationErrors | null> {
+    this.controls.forEach(control => control.validate())
+    return this._validate()
+  }
+
+  private _watchValid() {
+    watch(
+      [this.modelRef, this.blurred],
+      () => {
+        if (this.trigger === 'change' || (this.trigger === 'blur' && this.blurred.value)) {
+          this._validate()
+        }
+      },
+      { deep: true },
+    )
+  }
+
+  private _watchStatus() {
+    if (this._statusWatchStopHandle) {
+      this._statusWatchStopHandle()
+    }
+    this._statusWatchStopHandle = watchEffect(() => {
+      let status: ValidationStatus = this.errors.value ? 'invalid' : 'valid'
+      if (status === 'valid') {
+        for (const control of this.controls) {
+          const controlStatus = control.status.value
+          if (controlStatus === 'invalid') {
+            status = 'invalid'
+            break
+          } else if (controlStatus === 'validating') {
+            status = 'validating'
+          }
+        }
+      }
+      this._status.value = status
+    })
+  }
+
+  private _watchBlurred() {
+    if (this._blurredWatchStopHandle) {
+      this._blurredWatchStopHandle()
+    }
+    this._blurredWatchStopHandle = watchEffect(() => {
+      let blurred = false
+      for (const control of this.controls) {
+        if (control.blurred.value) {
+          blurred = true
+          break
+        }
+      }
+      this._blurred.value = blurred
+    })
+  }
+
+  private _calculateModelRef(controls: AbstractControl<T>[]) {
+    return controls.map(control => control.modelRef)
+  }
+
+  private _registerControl(control: AbstractControl<T>) {
+    control.setParent(this as any)
+  }
+}
diff --git a/packages/cdk/forms/src/controls/formControl.ts b/packages/cdk/forms/src/controls/formControl.ts
new file mode 100644
index 00000000..f9228da8
--- /dev/null
+++ b/packages/cdk/forms/src/controls/formControl.ts
@@ -0,0 +1,83 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { Ref, UnwrapRef } from 'vue'
+import type { AsyncValidatorFn, ValidatorFn, ValidatorOptions, ValidationErrors } from '../types'
+
+import { ref, watch } from 'vue'
+import { AbstractControl } from './abstractControl'
+
+export class FormControl<T = any> extends AbstractControl<T> {
+  /**
+   * The ref value for the control.
+   */
+  readonly modelRef: Ref<UnwrapRef<T> | null>
+
+  private _initValue: T | null = null
+  constructor(
+    initValue: T | null = null,
+    validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
+    asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+  ) {
+    super(validatorOrOptions, asyncValidator)
+    this.modelRef = ref(initValue)
+    this._initValue = initValue
+
+    this._watchEffect()
+  }
+
+  /**
+   * Resets the form control, marking it `unblurred`, and setting the value to initialization value.
+   */
+  reset(): void {
+    this.modelRef.value = this._initValue as any
+    this.markAsUnblurred()
+  }
+
+  /**
+   * Sets a new value for the form control.
+   *
+   * @param value The new value.
+   */
+  setValue(value: T | null): void {
+    this.modelRef.value = value as any
+  }
+
+  /**
+   * The aggregate value of the form control.
+   */
+  getValue(): T | null {
+    return this.modelRef.value as T
+  }
+
+  /**
+   * Marks the control as `blurred`.
+   */
+  markAsBlurred(): void {
+    this._blurred.value = true
+  }
+
+  /**
+   * Marks the control as `unblurred`.
+   */
+  markAsUnblurred(): void {
+    this._blurred.value = false
+  }
+
+  /**
+   * Running validations manually, rather than automatically.
+   */
+  async validate(): Promise<ValidationErrors | null> {
+    return this._validate()
+  }
+
+  private _watchEffect() {
+    watch([this.modelRef, this.blurred], () => {
+      if (this.trigger === 'change' || (this.trigger === 'blur' && this.blurred.value)) {
+        this._validate()
+      }
+    })
+
+    watch(this.errors, errors => {
+      this._status.value = errors ? 'invalid' : 'valid'
+    })
+  }
+}
diff --git a/packages/cdk/forms/src/controls/formGroup.ts b/packages/cdk/forms/src/controls/formGroup.ts
new file mode 100644
index 00000000..364a5071
--- /dev/null
+++ b/packages/cdk/forms/src/controls/formGroup.ts
@@ -0,0 +1,194 @@
+/* eslint-disable @typescript-eslint/no-non-null-assertion */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import type { Ref, UnwrapRef, WatchStopHandle } from 'vue'
+import type { AsyncValidatorFn, ValidatorFn, ValidatorOptions, ValidationErrors, ValidationStatus } from '../types'
+
+import { ref, watch, watchEffect } from 'vue'
+import { hasOwnProperty } from '@idux/cdk/utils'
+import { AbstractControl } from './abstractControl'
+
+export class FormGroup<T = Record<string, any>> extends AbstractControl<T> {
+  /**
+   * The ref value for the form group.
+   */
+  readonly modelRef: Ref<Partial<Record<keyof T, Ref<UnwrapRef<T>>>>>
+
+  private _statusWatchStopHandle: WatchStopHandle | null = null
+  private _blurredWatchStopHandle: WatchStopHandle | null = null
+
+  constructor(
+    public controls: Partial<Record<keyof T, AbstractControl>>,
+    validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
+    asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+  ) {
+    super(validatorOrOptions, asyncValidator)
+    this._forEachChild(control => control.setParent(this as any))
+    this.modelRef = ref(this._calculateModelRef(controls)) as Ref<Partial<Record<keyof T, Ref<UnwrapRef<T>>>>>
+
+    this._watchValid()
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Add a control to this form group.
+   *
+   * @param name The control name to add to the collection
+   * @param control Provides the control for the given name
+   */
+  addControl(name: keyof T, control: AbstractControl): void {
+    this._registerControl(name, control)
+    this.modelRef.value = this._calculateModelRef(this.controls)
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Remove a control from this form group.
+   *
+   * @param name The control name to remove from the collection
+   */
+  removeControl(name: keyof T): void {
+    delete this.controls[name]
+    this.modelRef.value = this._calculateModelRef(this.controls)
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Replace an existing control.
+   *
+   * @param name The control name to replace in the collection
+   * @param control Provides the control for the given name
+   */
+  setControl(name: keyof T, control: AbstractControl): void {
+    delete this.controls[name]
+    this._registerControl(name, control)
+    this.modelRef.value = this._calculateModelRef(this.controls)
+    this._watchStatus()
+    this._watchBlurred()
+  }
+
+  /**
+   * Resets all controls of the form group.
+   */
+  reset(): void {
+    this._forEachChild(control => control.reset())
+  }
+
+  /**
+   * Sets a new value for the form group.
+   *
+   * @param value The new value.
+   */
+  setValue(value: Partial<T>): void {
+    Object.keys(value).forEach(key => {
+      this.controls[key as keyof T]!.setValue((value as any)[key])
+    })
+  }
+
+  /**
+   * The aggregate value of the form control.
+   */
+  getValue(): T {
+    const value = {} as T
+    this._forEachChild((control, key) => {
+      value[key] = control.getValue()
+    })
+    return value
+  }
+
+  /**
+   * Marks all controls of the form group as `blurred`.
+   */
+  markAsBlurred(): void {
+    this._forEachChild(control => control.markAsBlurred())
+  }
+
+  /**
+   * Marks all controls of the form group as `unblurred`.
+   */
+  markAsUnblurred(): void {
+    this._forEachChild(control => control.markAsUnblurred())
+  }
+
+  /**
+   * Running validations manually, rather than automatically.
+   */
+  async validate(): Promise<ValidationErrors | null> {
+    this._forEachChild(control => control.validate())
+    return this._validate()
+  }
+
+  private _watchValid() {
+    watch(
+      [this.modelRef, this.blurred],
+      () => {
+        if (this.trigger === 'change' || (this.trigger === 'blur' && this.blurred.value)) {
+          this._validate()
+        }
+      },
+      { deep: true },
+    )
+  }
+
+  private _watchStatus() {
+    if (this._statusWatchStopHandle) {
+      this._statusWatchStopHandle()
+    }
+    this._statusWatchStopHandle = watchEffect(() => {
+      let status: ValidationStatus = this.errors.value ? 'invalid' : 'valid'
+      if (status === 'valid') {
+        for (const key in this.controls) {
+          const controlStatus = this.controls[key]!.status.value
+          if (controlStatus === 'invalid') {
+            status = 'invalid'
+            break
+          } else if (controlStatus === 'validating') {
+            status = 'validating'
+          }
+        }
+      }
+      this._status.value = status
+    })
+  }
+
+  private _watchBlurred() {
+    if (this._blurredWatchStopHandle) {
+      this._blurredWatchStopHandle()
+    }
+    this._blurredWatchStopHandle = watchEffect(() => {
+      let blurred = false
+      for (const key in this.controls) {
+        if (this.controls[key]!.blurred.value) {
+          blurred = true
+          break
+        }
+      }
+      this._blurred.value = blurred
+    })
+  }
+
+  private _calculateModelRef(controls: Partial<Record<keyof T, AbstractControl<T>>>) {
+    const model = {} as Partial<Record<keyof T, Ref<UnwrapRef<T>>>>
+
+    Object.keys(controls).forEach(key => {
+      model[key as keyof T] = controls[key as keyof T]!.modelRef
+    })
+
+    return model
+  }
+
+  private _registerControl(name: keyof T, control: AbstractControl<T>) {
+    if (hasOwnProperty(this.controls, name as string)) {
+      return
+    }
+    this.controls[name] = control
+    control.setParent(this as any)
+  }
+
+  private _forEachChild(cb: (v: AbstractControl, k: keyof T) => void): void {
+    Object.keys(this.controls).forEach(key => cb(this.controls[key as keyof T]!, key as keyof T))
+  }
+}
diff --git a/packages/cdk/forms/src/typeof.ts b/packages/cdk/forms/src/typeof.ts
index f808a763..085fa02b 100644
--- a/packages/cdk/forms/src/typeof.ts
+++ b/packages/cdk/forms/src/typeof.ts
@@ -1,7 +1,25 @@
-import { isNonNil } from '@idux/cdk/utils'
-import { ModelType } from './constant'
-import { FormControl } from './useFormControl'
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { FormArray } from './controls/formArray'
+import type { FormControl } from './controls/formControl'
+import type { FormGroup } from './controls/formGroup'
+
+import { hasOwnProperty, isArray } from '@idux/cdk/utils'
+import { AbstractControl } from './controls/abstractControl'
+
+export const isAbstractControl = (val: unknown): val is AbstractControl => {
+  return val instanceof AbstractControl
+}
+
+// Since AbstractControl be dependent on the function, `val instanceof FormArray` cannot be used here.
+export const isFormArray = (val: unknown): val is FormArray => {
+  return isAbstractControl(val) && hasOwnProperty(val, 'controls') && isArray((val as FormArray).controls)
+}
+
+// Since AbstractControl be dependent on the function, `val instanceof FormGroup` cannot be used here.
+export const isFormGroup = (val: unknown): val is FormGroup => {
+  return isAbstractControl(val) && hasOwnProperty(val, 'controls') && !isArray((val as FormGroup).controls)
+}
 
 export const isFormControl = (val: unknown): val is FormControl => {
-  return isNonNil(val) && (val as { __type: string }).__type === ModelType.Control
+  return isAbstractControl(val) && hasOwnProperty(val, '_initValue')
 }
diff --git a/packages/cdk/forms/src/types.ts b/packages/cdk/forms/src/types.ts
index 9706193a..60d74dfd 100644
--- a/packages/cdk/forms/src/types.ts
+++ b/packages/cdk/forms/src/types.ts
@@ -1,12 +1,14 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 
+import { AbstractControl } from './controls/abstractControl'
+
 export interface ValidationError {
   message: string
   actual?: any
   [key: string]: any
 }
 
-type ErrorMessage = string | ((info: Omit<ValidationError, 'message'>) => string)
+type ErrorMessage = string | ((err: Omit<ValidationError, 'message'>) => string)
 
 export interface ErrorMessages {
   default?: ErrorMessage
@@ -21,14 +23,14 @@ export interface ErrorMessages {
   [key: string]: ErrorMessage | undefined
 }
 
-export type Errors = { [key in keyof ErrorMessages]: ValidationError }
+export type ValidationErrors = { [key in keyof ErrorMessages]: ValidationError }
 
 export interface ValidatorFn {
-  (value: any): Errors | null
+  (value: any, control: AbstractControl): ValidationErrors | null
 }
 
 export interface AsyncValidatorFn {
-  (value: any): Promise<Errors | null>
+  (value: any, control: AbstractControl): Promise<ValidationErrors | null>
 }
 
 export type TriggerType = 'change' | 'blur' | 'submit'
@@ -39,4 +41,4 @@ export interface ValidatorOptions {
   trigger?: TriggerType
 }
 
-export type ValidStatus = 'valid' | 'invalid' | 'validating'
+export type ValidationStatus = 'valid' | 'invalid' | 'validating'
diff --git a/packages/cdk/forms/src/useFormControl.ts b/packages/cdk/forms/src/useFormControl.ts
deleted file mode 100644
index a3086290..00000000
--- a/packages/cdk/forms/src/useFormControl.ts
+++ /dev/null
@@ -1,255 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
-
-import type { ComputedRef, DeepReadonly, Ref, UnwrapRef } from 'vue'
-import type {
-  AsyncValidatorFn,
-  ValidatorFn,
-  ValidatorOptions,
-  TriggerType,
-  ValidationError,
-  ErrorMessages,
-  ValidStatus,
-  Errors,
-} from './types'
-
-import { computed, readonly, ref, watchEffect } from 'vue'
-import { isArray, isNil, isObject } from '@idux/cdk/utils'
-import { Validators } from './validators'
-import { ModelType } from './constant'
-
-export interface FormControl<T = any> {
-  /**
-   * The ref value for the control.
-   */
-  modelRef: Ref<UnwrapRef<T> | null>
-
-  /**
-   * The validation status of the control, there are three possible validation status values:
-   * * **valid**: This control has passed all validation checks.
-   * * **invalid**: This control has failed at least one validation check.
-   * * **validating**: This control is in the midst of conducting a validation check.
-   */
-  status: DeepReadonly<Ref<ValidStatus>>
-
-  /**
-   * An object containing any errors generated by failing validation, or null if there are no errors.
-   */
-  errors: DeepReadonly<Ref<Errors | null>>
-
-  /**
-   * Running validations manually, rather than automatically.
-   */
-  validate: () => Promise<Errors | null>
-
-  /**
-   * Resets the form control, marking it `unblurred`, and setting the value to initialization value.
-   */
-  reset: () => void
-
-  /**
-   * Sets a new value for the form control.
-   *
-   * @param value The new value.
-   */
-  setValue: (value: T | null) => void
-
-  /**
-   * Sets the new sync validator for the form control, it overwrites existing sync validators.
-   * If you want to clear all sync validators, you can pass in a null.
-   */
-  setValidator: (newValidator: ValidatorFn | ValidatorFn[] | null) => void
-
-  /**
-   * Sets the new async validator for the form control, it overwrites existing async validators.
-   * If you want to clear all async validators, you can pass in a null.
-   */
-  setAsyncValidator: (newAsyncValidator: AsyncValidatorFn | AsyncValidatorFn[] | null) => void
-
-  /**
-   * Sets errors on a form control when running validations manually, rather than automatically.
-   */
-  setErrors: (newErrors: Errors | null) => void
-
-  /**
-   * Reports error data for the control.
-   *
-   * @param errorCode The code of the error to check
-   */
-  getError: (errorCode: keyof ErrorMessages) => ValidationError | null
-
-  /**
-   * Reports whether the control has the error specified.
-   *
-   * @param errorCode The code of the error to check
-   */
-  hasError: (errorCode: keyof ErrorMessages) => boolean
-
-  /**
-   * Marks the control as `blurred`.
-   */
-  markAsBlurred: () => void
-
-  /**
-   * Marks the control as `unblurred`.
-   */
-  markAsUnblurred: () => void
-
-  /**
-   * A control is valid when its `status` is `valid`.
-   */
-  valid: ComputedRef<boolean>
-
-  /**
-   * A control is invalid when its `status` is `invalid`.
-   */
-  invalid: ComputedRef<boolean>
-
-  /**
-   * A control is validating when its `status` is `validating`.
-   */
-  validating: ComputedRef<boolean>
-
-  /**
-   * A control is marked `blurred` once the user has triggered a `blur` event on it.
-   */
-  blurred: ComputedRef<boolean>
-
-  /**
-   * A control is `unblurred` if the user has not yet triggered a `blur` event on it.
-   */
-  unblurred: ComputedRef<boolean>
-}
-
-export function useFormControl<T = any>(
-  initValue?: T | null,
-  validator?: ValidatorFn | ValidatorFn[] | null,
-  asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
-): FormControl<T>
-export function useFormControl<T = any>(initValue?: T | null, options?: ValidatorOptions | null): FormControl<T>
-export function useFormControl<T = any>(
-  initValue: T | null = null,
-  validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
-  asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
-): FormControl<T> {
-  const modelRef = ref(initValue)
-  const status = ref<ValidStatus>('valid')
-  const blurred = ref(false)
-  const errors = ref<Errors | null>(null)
-
-  let { _validator, _asyncValidator, _trigger } = convertOptions(validatorOrOptions, asyncValidator)
-
-  const reset = () => {
-    modelRef.value = initValue as any
-    markAsUnblurred()
-  }
-
-  const setValue = (value: T | null) => {
-    modelRef.value = value as any
-  }
-
-  const setValidator = (newValidator: ValidatorFn | ValidatorFn[] | null) => {
-    _validator = toValidator(newValidator)
-  }
-
-  const setAsyncValidator = (newAsyncValidator: AsyncValidatorFn | AsyncValidatorFn[] | null) => {
-    _asyncValidator = toAsyncValidator(newAsyncValidator)
-  }
-
-  const setErrors = (newErrors: Errors | null) => {
-    errors.value = newErrors
-    status.value = newErrors ? 'invalid' : 'valid'
-  }
-
-  const getError = (errorCode: keyof ErrorMessages) => {
-    return errors.value ? errors.value[errorCode] ?? null : null
-  }
-
-  const hasError = (errorCode: keyof ErrorMessages) => !!getError(errorCode)
-
-  const markAsBlurred = () => {
-    blurred.value = true
-  }
-
-  const markAsUnblurred = () => {
-    blurred.value = false
-  }
-
-  const _runValidator = () => {
-    return _validator ? _validator(modelRef.value) : null
-  }
-  const _runAsyncValidator = () => {
-    if (!_asyncValidator) {
-      return null
-    }
-    status.value = 'validating'
-    return _asyncValidator(modelRef.value)
-  }
-
-  const validate = async () => {
-    let newErrors = _runValidator()
-    if (isNil(newErrors)) {
-      newErrors = await _runAsyncValidator()
-    }
-    setErrors(newErrors)
-    return newErrors
-  }
-
-  watchEffect(() => {
-    if (_trigger === 'change' || (_trigger === 'blur' && blurred.value)) {
-      validate()
-    }
-  })
-
-  return {
-    __type: ModelType.Control,
-    modelRef,
-    status: readonly(status),
-    errors: readonly(errors),
-    validate,
-    reset,
-    setValue,
-    setValidator,
-    setAsyncValidator,
-    setErrors,
-    getError,
-    hasError,
-    markAsBlurred,
-    markAsUnblurred,
-    valid: computed(() => status.value === 'valid'),
-    invalid: computed(() => status.value === 'invalid'),
-    validating: computed(() => status.value === 'validating'),
-    blurred: computed(() => blurred.value),
-    unblurred: computed(() => !blurred.value),
-  } as FormControl<T>
-}
-
-function toValidator(validator?: ValidatorFn | ValidatorFn[] | null): ValidatorFn | null {
-  return isArray(validator) ? Validators.compose(validator) : validator || null
-}
-
-function toAsyncValidator(asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): AsyncValidatorFn | null {
-  return isArray(asyncValidator) ? Validators.composeAsync(asyncValidator) : asyncValidator || null
-}
-
-function isOptions(val?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null): val is ValidatorOptions {
-  return isObject(val) && !isArray(val)
-}
-
-function convertOptions(
-  validatorOrOptions: ValidatorFn | ValidatorFn[] | ValidatorOptions | null | undefined,
-  asyncValidator: AsyncValidatorFn | AsyncValidatorFn[] | null | undefined,
-) {
-  let _trigger: TriggerType = 'change'
-  let _validator: ValidatorFn | null
-  let _asyncValidator: AsyncValidatorFn | null
-  if (isOptions(validatorOrOptions)) {
-    _trigger = validatorOrOptions.trigger ?? _trigger
-    _validator = toValidator(validatorOrOptions.validators)
-    _asyncValidator = toAsyncValidator(validatorOrOptions.asyncValidators)
-  } else {
-    _validator = toValidator(validatorOrOptions)
-    _asyncValidator = toAsyncValidator(asyncValidator)
-  }
-  return { _validator, _asyncValidator, _trigger }
-}
diff --git a/packages/cdk/forms/src/useForms.ts b/packages/cdk/forms/src/useForms.ts
new file mode 100644
index 00000000..7d56ab10
--- /dev/null
+++ b/packages/cdk/forms/src/useForms.ts
@@ -0,0 +1,69 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import type { AsyncValidatorFn, ValidatorFn, ValidatorOptions } from './types'
+import type { AbstractControl } from './controls/abstractControl'
+
+import { isArray } from '@idux/cdk/utils'
+import { FormGroup } from './controls/formGroup'
+import { FormControl } from './controls/formControl'
+import { FormArray } from './controls/formArray'
+import { isAbstractControl } from './typeof'
+
+type ControlConfig<T> = [
+  T | null | undefined,
+  ValidatorFn | ValidatorFn[] | ValidatorOptions | null | undefined,
+  AsyncValidatorFn | AsyncValidatorFn[] | null | undefined,
+]
+
+export function useFormGroup<T = any>(
+  controlsConfig: Partial<Record<keyof T, AbstractControl<T> | ControlConfig<T> | any>>,
+  validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
+  asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+): FormGroup<T> {
+  const controls = reduceControls(controlsConfig)
+  return new FormGroup(controls, validatorOrOptions, asyncValidator)
+}
+
+export function useFormControl<T = any>(
+  initValue?: T | null,
+  validator?: ValidatorFn | ValidatorFn[] | null,
+  asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+): FormControl<T>
+export function useFormControl<T = any>(initValue?: T | null, options?: ValidatorOptions | null): FormControl<T>
+export function useFormControl<T = any>(
+  initValue: T | null = null,
+  validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
+  asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+): FormControl<T> {
+  return new FormControl(initValue, validatorOrOptions, asyncValidator)
+}
+
+export function useFormArray<T = any>(
+  controlsConfig: Array<AbstractControl<T> | ControlConfig<T> | any>,
+  validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
+  asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+): FormArray<T> {
+  const controls = controlsConfig.map(config => createControl<T>(config))
+  return new FormArray(controls, validatorOrOptions, asyncValidator)
+}
+
+function reduceControls<T>(
+  controlsConfig: Partial<Record<keyof T, AbstractControl<T> | ControlConfig<T> | any>>,
+): Partial<Record<keyof T, AbstractControl<T>>> {
+  const controls = {} as Partial<Record<keyof T, AbstractControl<T>>>
+  Object.keys(controlsConfig).forEach(controlName => {
+    controls[controlName as keyof Required<T>] = createControl(controlsConfig[controlName as keyof Required<T>])
+  })
+  return controls
+}
+
+function createControl<T>(controlConfig: AbstractControl<T> | ControlConfig<T> | any): AbstractControl<T> {
+  if (isAbstractControl(controlConfig)) {
+    return controlConfig
+  } else if (isArray(controlConfig)) {
+    const [initValue, validatorOrOptions, asyncValidator] = controlConfig
+    return useFormControl(initValue, validatorOrOptions, asyncValidator)
+  } else {
+    return useFormControl(controlConfig)
+  }
+}
diff --git a/packages/cdk/forms/src/validators.ts b/packages/cdk/forms/src/validators.ts
index 3bfc2fc2..9e2f5824 100644
--- a/packages/cdk/forms/src/validators.ts
+++ b/packages/cdk/forms/src/validators.ts
@@ -1,7 +1,8 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 
-import type { AsyncValidatorFn, ValidationError, ValidatorFn, Errors, ErrorMessages } from './types'
+import type { AsyncValidatorFn, ValidationError, ValidatorFn, ValidationErrors, ErrorMessages } from './types'
+import type { AbstractControl } from './controls/abstractControl'
 
 import { isArray, isFunction, isNil, isNonNil, isNumber, isNumeric, isString } from '@idux/cdk/utils'
 
@@ -26,21 +27,21 @@ export class Validators {
     Validators.messages = { ...Validators.messages, ...messages }
   }
 
-  static required(value: any): { required: ValidationError } | null {
+  static required(value: any, _: AbstractControl): { required: ValidationError } | null {
     if (isEmpty(value)) {
       return { required: Validators.getMessage('required', {}) }
     }
     return null
   }
 
-  static requiredTrue(value: any): { requiredTrue: ValidationError } | null {
+  static requiredTrue(value: any, _: AbstractControl): { requiredTrue: ValidationError } | null {
     if (value === true) {
       return null
     }
     return { requiredTrue: Validators.getMessage('requiredTrue', { actual: value }) }
   }
 
-  static email(value: any): { email: ValidationError } | null {
+  static email(value: any, _: AbstractControl): { email: ValidationError } | null {
     if (isEmpty(value) || emailRegexp.test(value)) {
       return null
     }
@@ -48,7 +49,7 @@ export class Validators {
   }
 
   static min(min: number): ValidatorFn {
-    return (value: any): { min: ValidationError } | null => {
+    return (value: any, _: AbstractControl): { min: ValidationError } | null => {
       if (isEmpty(value) || !isNumeric(value) || Number(value) >= min) {
         return null
       }
@@ -57,7 +58,7 @@ export class Validators {
   }
 
   static max(max: number): ValidatorFn {
-    return (value: any): { max: ValidationError } | null => {
+    return (value: any, _: AbstractControl): { max: ValidationError } | null => {
       if (isEmpty(value) || !isNumeric(value) || Number(value) <= max) {
         return null
       }
@@ -66,7 +67,7 @@ export class Validators {
   }
 
   static minLength(minLength: number): ValidatorFn {
-    return (value: any): { minLength: ValidationError } | null => {
+    return (value: any, _: AbstractControl): { minLength: ValidationError } | null => {
       if (isEmpty(value) || !hasLength(value) || value.length >= minLength) {
         return null
       }
@@ -75,7 +76,7 @@ export class Validators {
   }
 
   static maxLength(maxLength: number): ValidatorFn {
-    return (value: any): { maxLength: ValidationError } | null => {
+    return (value: any, _: AbstractControl): { maxLength: ValidationError } | null => {
       if (isEmpty(value) || !hasLength(value) || value.length <= maxLength) {
         return null
       }
@@ -103,7 +104,7 @@ export class Validators {
       regexStr = pattern.toString()
       regex = pattern
     }
-    return (value: any): { pattern: ValidationError } | null => {
+    return (value: any, _: AbstractControl): { pattern: ValidationError } | null => {
       if (isEmpty(value) || regex.test(value)) {
         return null
       }
@@ -111,7 +112,7 @@ export class Validators {
     }
   }
 
-  static nullValidator(_: any): null {
+  static nullValidator(_: any, __: AbstractControl): null {
     return null
   }
 
@@ -124,7 +125,8 @@ export class Validators {
       return null
     }
 
-    return (value: any) => mergeMessages(executeValidators<ValidatorFn>(value, presentValidators))
+    return (value: any, control: AbstractControl) =>
+      mergeMessages(executeValidators<ValidatorFn>(value, control, presentValidators))
   }
 
   static composeAsync(validators: (AsyncValidatorFn | null)[] | null): AsyncValidatorFn | null {
@@ -136,8 +138,8 @@ export class Validators {
       return null
     }
 
-    return (value: any) => {
-      const validationErrors = executeValidators<AsyncValidatorFn>(value, presentValidators)
+    return (value: any, control: AbstractControl) => {
+      const validationErrors = executeValidators<AsyncValidatorFn>(value, control, presentValidators)
       return Promise.all(validationErrors).then(mergeMessages)
     }
   }
@@ -153,18 +155,22 @@ function hasLength(val: any): boolean {
   return isNonNil(val) && isNumber(val.length)
 }
 
-type GenericValidatorFn = (value: any) => any
+type GenericValidatorFn = (value: any, _: AbstractControl) => any
 
-function executeValidators<V extends GenericValidatorFn>(value: any, validators: V[]): ReturnType<V>[] {
-  return validators.map(validator => validator(value))
+function executeValidators<V extends GenericValidatorFn>(
+  value: any,
+  control: AbstractControl,
+  validators: V[],
+): ReturnType<V>[] {
+  return validators.map(validator => validator(value, control))
 }
 
-function mergeMessages(validationErrors: (Errors | null)[]): Errors | null {
+function mergeMessages(validationErrors: (ValidationErrors | null)[]): ValidationErrors | null {
   let res: { [key: string]: any } = {}
 
   // Not using Array.reduce here due to a Chrome 80 bug
   // https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
-  validationErrors.forEach((errors: Errors | null) => {
+  validationErrors.forEach((errors: ValidationErrors | null) => {
     res = isNonNil(errors) ? { ...res, ...errors } : res
   })
 
diff --git a/packages/cdk/utils/typeof.ts b/packages/cdk/utils/typeof.ts
index 99257013..5a1d1f9e 100644
--- a/packages/cdk/utils/typeof.ts
+++ b/packages/cdk/utils/typeof.ts
@@ -69,6 +69,7 @@ export function isPromise<T = any>(val: unknown): val is Promise<T> {
   return isObject(val) && isFunction(val.then) && isFunction(val.catch)
 }
 
-export function hasOwnProperty(val: Record<string | symbol, unknown>, key: string | symbol): key is keyof typeof val {
+// eslint-disable-next-line @typescript-eslint/ban-types
+export function hasOwnProperty(val: object, key: string | symbol): key is keyof typeof val {
   return Object.prototype.hasOwnProperty.call(val, key)
 }


Test patch:
diff --git a/packages/cdk/forms/__tests__/abstractControl.spec.ts b/packages/cdk/forms/__tests__/abstractControl.spec.ts
new file mode 100644
index 00000000..c0eaa3f9
--- /dev/null
+++ b/packages/cdk/forms/__tests__/abstractControl.spec.ts
@@ -0,0 +1,155 @@
+import { flushPromises } from '@vue/test-utils'
+import { Ref, ref } from 'vue'
+import { AbstractControl } from '../src/controls/abstractControl'
+import { AsyncValidatorFn, ValidationErrors, ValidatorFn, ValidatorOptions } from '../src/types'
+import { Validators } from '../src/validators'
+
+class Control<T = unknown> extends AbstractControl<T> {
+  readonly modelRef: Ref<T | null> = ref(null)
+  constructor(
+    validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions | null,
+    asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
+  ) {
+    super(validatorOrOptions, asyncValidator)
+  }
+  reset(): void {}
+  setValue(_: T | null): void {}
+  getValue(): T {
+    return this.modelRef.value as T
+  }
+  markAsBlurred(): void {}
+  markAsUnblurred(): void {}
+  async validate(): Promise<ValidationErrors | null> {
+    return this._validate()
+  }
+}
+
+describe('abstractControl.ts', () => {
+  describe('basic work', () => {
+    let control: Control
+
+    beforeEach(() => {
+      control = new Control()
+    })
+
+    test('init status work', () => {
+      expect(control.status.value).toEqual('valid')
+      expect(control.valid.value).toEqual(true)
+      expect(control.invalid.value).toEqual(false)
+      expect(control.validating.value).toEqual(false)
+    })
+
+    test('init errors work', () => {
+      expect(control.errors.value).toBeNull()
+    })
+
+    test('init blurred work', () => {
+      expect(control.blurred.value).toEqual(false)
+      expect(control.unblurred.value).toEqual(true)
+    })
+
+    test('init props work', () => {
+      expect(control.parent).toBeNull()
+      expect(control.root).toEqual(control)
+      expect(control.trigger).toEqual('change')
+    })
+
+    test('setValidator work', async () => {
+      const { required, minLength, email } = Validators
+      control.setValidator(required)
+
+      expect(await control.validate()).toEqual({ required: { message: '' } })
+
+      control.setValidator([email, minLength(5)])
+      control.modelRef.value = 'test'
+
+      expect(await control.validate()).toEqual({
+        email: { message: '', actual: 'test' },
+        minLength: { message: '', minLength: 5, actual: 4 },
+      })
+    })
+
+    test('setAsyncValidator work', async () => {
+      const _asyncValidator = (key: string, error: unknown): AsyncValidatorFn => {
+        return (_: unknown) => {
+          return Promise.resolve({ [key]: error } as ValidationErrors)
+        }
+      }
+      const message1 = { message: 1 }
+      const message2 = { message: 2 }
+
+      control.setAsyncValidator(_asyncValidator('a', message1))
+
+      expect(await control.validate()).toEqual({ a: message1 })
+
+      control.setAsyncValidator([_asyncValidator('a', message1), _asyncValidator('b', message2)])
+
+      expect(await control.validate()).toEqual({ a: message1, b: message2 })
+    })
+
+    test('setErrors, getError and hasError work', () => {
+      expect(control.errors.value).toBeNull()
+      expect(control.getError('required')).toBeNull()
+      expect(control.hasError('required')).toEqual(false)
+
+      const errors = { required: { message: '' } }
+      control.setErrors(errors)
+
+      expect(control.errors.value).toEqual(errors)
+
+      expect(control.getError('required')).toEqual(errors.required)
+      expect(control.getError('max')).toBeNull()
+
+      expect(control.hasError('required')).toEqual(true)
+      expect(control.hasError('max')).toEqual(false)
+    })
+
+    test('setParent work', () => {
+      const parent = new Control()
+
+      expect(control.parent).not.toEqual(parent)
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      control.setParent(parent as any)
+      expect(control.parent).toEqual(parent)
+      expect(control.root).toEqual(parent)
+
+      const root = new Control()
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      parent.setParent(root as any)
+      expect(control.root).toEqual(root)
+    })
+  })
+
+  describe('convert options work', () => {
+    let control: Control
+
+    test('options work', async () => {
+      control = new Control({ validators: Validators.required })
+
+      expect(await control.validate()).toEqual({ required: { message: '' } })
+
+      expect(control.trigger).toEqual('change')
+
+      control = new Control({ trigger: 'blur' })
+
+      expect(control.trigger).toEqual('blur')
+    })
+
+    test('validators work', async () => {
+      const _asyncValidator = (_: unknown) => Promise.resolve({ async: { message: 'async' } } as ValidationErrors)
+
+      control = new Control(Validators.required, _asyncValidator)
+      expect(await control.validate()).toEqual({ required: { message: '' } })
+
+      control.modelRef.value = 'test'
+      control.validate()
+
+      expect(control.status.value).toEqual('validating')
+      expect(control.validating.value).toEqual(true)
+
+      await flushPromises()
+
+      expect(control.hasError('async')).toEqual(true)
+    })
+  })
+})
diff --git a/packages/cdk/forms/__tests__/formArray.spec.ts b/packages/cdk/forms/__tests__/formArray.spec.ts
new file mode 100644
index 00000000..17c39341
--- /dev/null
+++ b/packages/cdk/forms/__tests__/formArray.spec.ts
@@ -0,0 +1,272 @@
+import { flushPromises } from '@vue/test-utils'
+import { FormArray } from '../src/controls/formArray'
+import { FormControl } from '../src/controls/formControl'
+import { FormGroup } from '../src/controls/formGroup'
+import { ValidationErrors } from '../src/types'
+import { Validators } from '../src/validators'
+
+interface BasicGroup {
+  control: string
+  array: string[]
+  group: {
+    control: string
+  }
+}
+
+const newFormGroup = () =>
+  new FormGroup({
+    control: new FormControl(''),
+    array: new FormArray([new FormControl(''), new FormControl()]),
+    group: new FormGroup({
+      control: new FormControl(),
+    }),
+  })
+
+const basicValue = { control: '', array: ['', null], group: { control: null } }
+
+describe('formArray.ts', () => {
+  describe('basic work', () => {
+    let array: FormArray<BasicGroup>
+
+    beforeEach(() => {
+      array = new FormArray([newFormGroup()])
+    })
+
+    test('push work', async () => {
+      const group = newFormGroup()
+
+      expect(array.length).toEqual(1)
+      expect(array.getValue()).toEqual([basicValue])
+
+      array.push(group)
+
+      expect(array.length).toEqual(2)
+      expect(array.at(0)).not.toEqual(group)
+      expect(array.at(1)).toEqual(group)
+      expect(array.getValue()).toEqual([basicValue, basicValue])
+
+      group.markAsBlurred()
+      await flushPromises()
+
+      expect(array.blurred.value).toEqual(true)
+    })
+
+    test('insert work', async () => {
+      const group1 = newFormGroup()
+      const group2 = newFormGroup()
+
+      expect(array.length).toEqual(1)
+      expect(array.getValue()).toEqual([basicValue])
+
+      array.insert(0, group1)
+
+      expect(array.length).toEqual(2)
+      expect(array.at(0)).toEqual(group1)
+      expect(array.getValue()).toEqual([basicValue, basicValue])
+
+      array.insert(0, group2)
+
+      expect(array.length).toEqual(3)
+      expect(array.at(0)).toEqual(group2)
+      expect(array.getValue()).toEqual([basicValue, basicValue, basicValue])
+
+      group1.markAsBlurred()
+      await flushPromises()
+      expect(array.blurred.value).toEqual(true)
+    })
+
+    test('removeAt work', async () => {
+      expect(array.length).toEqual(1)
+      expect(array.getValue()).toEqual([basicValue])
+
+      array.removeAt(1)
+
+      expect(array.length).toEqual(1)
+      expect(array.getValue()).toEqual([basicValue])
+
+      const group = array.at(0)
+      array.removeAt(0)
+      expect(array.length).toEqual(0)
+      expect(array.getValue()).toEqual([])
+
+      group.markAsBlurred()
+      await flushPromises()
+      expect(array.blurred.value).toEqual(false)
+    })
+
+    test('setControl work', async () => {
+      expect(array.getValue()).toEqual([basicValue])
+      const group = new FormGroup({
+        control: new FormControl('test'),
+        array: new FormArray([new FormControl(''), new FormControl()]),
+        group: new FormGroup({
+          control: new FormControl(),
+        }),
+      })
+      array.setControl(0, group)
+
+      expect(array.getValue()).toEqual([{ ...basicValue, control: 'test' }])
+
+      group.markAsBlurred()
+      await flushPromises()
+      expect(array.blurred.value).toEqual(true)
+    })
+
+    test('reset work', async () => {
+      expect(array.getValue()).toEqual([basicValue])
+
+      array.setValue([{ control: 'test' }])
+      array.markAsBlurred()
+      await flushPromises()
+
+      expect(array.getValue()).toEqual([{ ...basicValue, control: 'test' }])
+      expect(array.blurred.value).toEqual(true)
+
+      array.reset()
+      await flushPromises()
+
+      expect(array.getValue()).toEqual([basicValue])
+      expect(array.blurred.value).toEqual(false)
+    })
+
+    test('setValue and getValue work', () => {
+      expect(array.getValue()).toEqual([basicValue])
+
+      array.setValue([{ control: 'test' }])
+
+      expect(array.getValue()).toEqual([{ ...basicValue, control: 'test' }])
+
+      array.setValue([{ control: 'test1' }, { control: 'test2' }])
+
+      expect(array.getValue()).toEqual([{ ...basicValue, control: 'test1' }])
+    })
+
+    test('markAsBlurred and markAsUnblurred work', async () => {
+      array.markAsBlurred()
+
+      await flushPromises()
+
+      expect(array.blurred.value).toEqual(true)
+
+      array.markAsUnblurred()
+
+      await flushPromises()
+
+      expect(array.blurred.value).toEqual(false)
+    })
+
+    test('validate work', async () => {
+      expect(await array.validate()).toBeNull()
+
+      const _validator = (_: unknown) => ({ test: { message: '' } } as ValidationErrors)
+
+      array.setValidator(_validator)
+
+      expect(await array.validate()).toEqual({ test: { message: '' } })
+    })
+
+    test('get work', async () => {
+      const [group] = array.controls
+
+      expect(array.get('0')).toEqual(group)
+      expect(array.get([0])).toEqual(group)
+
+      expect(array.get(undefined as never)).toBeNull()
+      expect(array.get('1')).toBeNull()
+      expect(array.get([1])).toBeNull()
+    })
+  })
+
+  describe('trigger work', () => {
+    let array: FormArray<BasicGroup>
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const _validator = (value: any) => {
+      return value[0].control === 'test' ? null : ({ test: { message: '' } } as ValidationErrors)
+    }
+
+    test('default change work', async () => {
+      array = new FormArray([newFormGroup()], { validators: _validator })
+
+      array.setValue([{ control: '1234' }])
+      await flushPromises()
+
+      expect(array.invalid.value).toEqual(true)
+      expect(array.hasError('test')).toEqual(true)
+
+      array.setValue([{ control: 'test' }])
+      await flushPromises()
+
+      expect(array.invalid.value).toEqual(false)
+      expect(array.hasError('test')).toEqual(false)
+    })
+
+    test('blur trigger validate work', async () => {
+      array = new FormArray([newFormGroup()], { trigger: 'blur', validators: _validator })
+
+      array.setValue([{ control: '1234' }])
+      await flushPromises()
+
+      expect(array.invalid.value).toEqual(false)
+      expect(array.hasError('test')).toEqual(false)
+
+      array.markAsBlurred()
+      await flushPromises()
+
+      expect(array.invalid.value).toEqual(true)
+      expect(array.hasError('test')).toEqual(true)
+
+      array.setValue([{ control: 'test' }])
+      await flushPromises()
+
+      expect(array.invalid.value).toEqual(false)
+      expect(array.hasError('test')).toEqual(false)
+    })
+
+    test('submit trigger validate work', async () => {
+      const _asyncValidator = (_: unknown) => Promise.resolve({ async: { message: 'async' } } as ValidationErrors)
+      array = new FormArray(
+        [
+          new FormGroup<BasicGroup>({
+            control: new FormControl('', Validators.required),
+            array: new FormArray([new FormControl(''), new FormControl()]),
+            group: new FormGroup({
+              control: new FormControl(null, { asyncValidators: _asyncValidator }),
+            }),
+          }),
+        ],
+        { trigger: 'submit', validators: _validator },
+      )
+
+      array.setValue([{ control: '' }])
+      array.markAsBlurred()
+      await flushPromises()
+
+      expect(array.invalid.value).toEqual(false)
+      expect(array.hasError('test')).toEqual(false)
+      expect(array.hasError('required', [0, 'control'])).toEqual(false)
+      expect(array.hasError('async', [0, 'group', 'control'])).toEqual(false)
+
+      await array.validate()
+
+      expect(array.invalid.value).toEqual(true)
+      expect(array.hasError('test')).toEqual(true)
+      expect(array.hasError('required', [0, 'control'])).toEqual(true)
+      expect(array.hasError('async', [0, 'group', 'control'])).toEqual(true)
+
+      array.setValue([{ control: 'test' }])
+      await flushPromises()
+
+      expect(array.invalid.value).toEqual(true)
+      expect(array.hasError('test')).toEqual(true)
+      expect(array.hasError('required', [0, 'control'])).toEqual(true)
+      expect(array.hasError('async', [0, 'group', 'control'])).toEqual(true)
+
+      await array.validate()
+
+      expect(array.invalid.value).toEqual(true)
+      expect(array.hasError('test')).toEqual(false)
+      expect(array.hasError('required', [0, 'control'])).toEqual(false)
+      expect(array.hasError('async', [0, 'group', 'control'])).toEqual(true)
+    })
+  })
+})
diff --git a/packages/cdk/forms/__tests__/formControl.spec.ts b/packages/cdk/forms/__tests__/formControl.spec.ts
new file mode 100644
index 00000000..219c78ba
--- /dev/null
+++ b/packages/cdk/forms/__tests__/formControl.spec.ts
@@ -0,0 +1,102 @@
+import { flushPromises } from '@vue/test-utils'
+import { FormControl } from '../src/controls/formControl'
+import { Validators } from '../src/validators'
+
+describe('formControl.ts', () => {
+  describe('basic work', () => {
+    let control: FormControl
+
+    beforeEach(() => {
+      control = new FormControl()
+    })
+
+    test('reset work', async () => {
+      control.setValue('test')
+      control.markAsBlurred()
+
+      expect(control.modelRef.value).toEqual('test')
+      expect(control.blurred.value).toEqual(true)
+
+      control.reset()
+
+      expect(control.modelRef.value).toBeNull()
+      expect(control.blurred.value).toEqual(false)
+    })
+
+    test('setValue and getValue work', () => {
+      expect(control.modelRef.value).toBeNull()
+
+      control.setValue('test')
+
+      expect(control.getValue()).toEqual('test')
+
+      control.setValue('')
+
+      expect(control.getValue()).toEqual('')
+    })
+
+    test('markAsBlurred and markAsUnblurred work', () => {
+      control.markAsBlurred()
+
+      expect(control.blurred.value).toEqual(true)
+
+      control.markAsUnblurred()
+
+      expect(control.blurred.value).toEqual(false)
+    })
+
+    test('validate work', async () => {
+      expect(await control.validate()).toBeNull()
+
+      control.setValidator(Validators.required)
+
+      expect(await control.validate()).toEqual({ required: { message: '' } })
+    })
+  })
+
+  describe('trigger work', () => {
+    let control: FormControl
+
+    test('default change work', async () => {
+      control = new FormControl(null, { validators: Validators.required })
+
+      expect(control.hasError('required')).toEqual(false)
+
+      control.setValue('')
+      await flushPromises()
+
+      expect(control.hasError('required')).toEqual(true)
+    })
+
+    test('blur trigger validate work', async () => {
+      control = new FormControl(null, { trigger: 'blur', validators: Validators.required })
+
+      expect(control.hasError('required')).toEqual(false)
+
+      control.markAsBlurred()
+      await flushPromises()
+
+      expect(control.hasError('required')).toEqual(true)
+
+      control.setValue('test')
+      await flushPromises()
+
+      expect(control.hasError('required')).toEqual(false)
+    })
+
+    test('submit trigger validate work', async () => {
+      control = new FormControl(null, { trigger: 'submit', validators: Validators.required })
+
+      expect(control.hasError('required')).toEqual(false)
+
+      control.markAsBlurred()
+      await flushPromises()
+
+      expect(control.hasError('required')).toEqual(false)
+
+      await control.validate()
+
+      expect(control.hasError('required')).toEqual(true)
+    })
+  })
+})
diff --git a/packages/cdk/forms/__tests__/formGroup.spec.ts b/packages/cdk/forms/__tests__/formGroup.spec.ts
new file mode 100644
index 00000000..38044e4f
--- /dev/null
+++ b/packages/cdk/forms/__tests__/formGroup.spec.ts
@@ -0,0 +1,277 @@
+import { flushPromises } from '@vue/test-utils'
+import { FormArray } from '../src/controls/formArray'
+import { FormControl } from '../src/controls/formControl'
+import { FormGroup } from '../src/controls/formGroup'
+import { ValidationErrors } from '../src/types'
+import { Validators } from '../src/validators'
+
+interface BasicGroup {
+  control: string
+  array: string[]
+  group: {
+    control: string
+  }
+  add?: string
+}
+
+const basicValue = { control: '', array: ['', null], group: { control: null } }
+
+describe('formGroup.ts', () => {
+  describe('basic work', () => {
+    let group: FormGroup<BasicGroup>
+
+    beforeEach(() => {
+      group = new FormGroup<BasicGroup>({
+        control: new FormControl(''),
+        array: new FormArray([new FormControl(''), new FormControl()]),
+        group: new FormGroup({
+          control: new FormControl(),
+        }),
+      })
+    })
+
+    test('addControl work', async () => {
+      expect(group.getValue()).toEqual(basicValue)
+
+      const control = new FormControl('')
+      group.addControl('add', control)
+      control.markAsBlurred()
+      await flushPromises()
+
+      expect(group.blurred.value).toEqual(true)
+      expect(group.getValue()).toEqual({ ...basicValue, add: '' })
+
+      group.addControl('add', new FormControl('test'))
+
+      expect(group.getValue()).toEqual({ ...basicValue, add: '' })
+    })
+
+    test('removeControl work', async () => {
+      expect(group.getValue()).toEqual(basicValue)
+
+      const _control = group.get('control')
+      group.removeControl('control')
+      const { control, ...rest } = group.getValue()
+
+      expect(group.getValue()).toEqual(rest)
+
+      _control?.markAsBlurred()
+      await flushPromises()
+      expect(group.blurred.value).toEqual(false)
+    })
+
+    test('setControl work', async () => {
+      expect(group.getValue()).toEqual(basicValue)
+
+      const _control = new FormControl('test')
+      group.setControl('control', _control)
+
+      expect(group.getValue()).toEqual({ ...basicValue, control: 'test' })
+
+      _control?.markAsBlurred()
+      await flushPromises()
+      expect(group.blurred.value).toEqual(true)
+    })
+
+    test('reset work', async () => {
+      expect(group.getValue()).toEqual(basicValue)
+
+      group.setValue({ control: 'test' })
+      group.markAsBlurred()
+      await flushPromises()
+
+      expect(group.getValue()).toEqual({ ...basicValue, control: 'test' })
+      expect(group.blurred.value).toEqual(true)
+
+      group.reset()
+      await flushPromises()
+
+      expect(group.getValue()).toEqual(basicValue)
+      expect(group.blurred.value).toEqual(false)
+    })
+
+    test('setValue and getValue work', () => {
+      expect(group.getValue()).toEqual(basicValue)
+
+      group.setValue({ control: 'test' })
+
+      expect(group.getValue()).toEqual({ ...basicValue, control: 'test' })
+
+      group.setValue({ control: 'test1' })
+
+      expect(group.getValue()).toEqual({ ...basicValue, control: 'test1' })
+    })
+
+    test('markAsBlurred and markAsUnblurred work', async () => {
+      group.markAsBlurred()
+
+      await flushPromises()
+
+      expect(group.blurred.value).toEqual(true)
+
+      group.markAsUnblurred()
+
+      await flushPromises()
+
+      expect(group.blurred.value).toEqual(false)
+    })
+
+    test('validate work', async () => {
+      expect(await group.validate()).toBeNull()
+
+      const _validator = (_: unknown) => ({ test: { message: '' } } as ValidationErrors)
+
+      group.setValidator(_validator)
+
+      expect(await group.validate()).toEqual({ test: { message: '' } })
+    })
+
+    test('get work', async () => {
+      const { control, array, group: groupChild } = group.controls
+      expect(group.get('control')).toEqual(control)
+      expect(group.get('array')).toEqual(array)
+      expect(group.get('group')).toEqual(groupChild)
+      expect(group.get('group.control')).toEqual((groupChild as FormGroup).controls.control)
+      expect(group.get(['array', 0])).toEqual((array as FormArray).controls[0])
+
+      expect(group.get(undefined as never)).toBeNull()
+      expect(group.get('')).toBeNull()
+      expect(group.get([])).toBeNull()
+      expect(group.get('add')).toBeNull()
+      expect(group.get(['array', 2])).toBeNull()
+      expect(group.get('group.control.test')).toBeNull()
+    })
+  })
+
+  describe('trigger work', () => {
+    let group: FormGroup<BasicGroup>
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const _validator = (value: any) => {
+      return value.control === 'test' ? null : ({ test: { message: '' } } as ValidationErrors)
+    }
+
+    test('default change work', async () => {
+      group = new FormGroup<BasicGroup>({
+        control: new FormControl(null, Validators.required),
+        array: new FormArray([new FormControl(''), new FormControl()]),
+        group: new FormGroup({
+          control: new FormControl(),
+        }),
+      })
+
+      group.setValue({ control: '' })
+      await flushPromises()
+
+      expect(group.invalid.value).toEqual(true)
+      expect(group.hasError('required', 'control')).toEqual(true)
+      expect(group.hasError('test')).toEqual(false)
+
+      group.setValidator(_validator)
+      group.setValue({ control: '1234' })
+      await flushPromises()
+
+      expect(group.invalid.value).toEqual(true)
+      expect(group.hasError('required', 'control')).toEqual(false)
+      expect(group.hasError('test')).toEqual(true)
+
+      group.setValue({ control: 'test' })
+      await flushPromises()
+
+      expect(group.invalid.value).toEqual(false)
+      expect(group.hasError('test')).toEqual(false)
+      expect(group.hasError('required', 'control')).toEqual(false)
+    })
+
+    test('blur trigger validate work', async () => {
+      group = new FormGroup<BasicGroup>(
+        {
+          control: new FormControl('', Validators.required),
+          array: new FormArray([new FormControl(''), new FormControl()]),
+          group: new FormGroup({
+            control: new FormControl('', {
+              validators: Validators.required,
+              trigger: 'submit',
+            }),
+          }),
+        },
+        { trigger: 'blur', validators: _validator },
+      )
+
+      group.setValue({ control: '' })
+
+      await flushPromises()
+
+      expect(group.invalid.value).toEqual(false)
+      expect(group.hasError('test')).toEqual(false)
+      expect(group.hasError('required', 'control')).toEqual(false)
+      expect(group.hasError('required', 'group.control')).toEqual(false)
+
+      group.markAsBlurred()
+      await flushPromises()
+
+      expect(group.invalid.value).toEqual(true)
+      expect(group.hasError('test')).toEqual(true)
+      expect(group.hasError('required', 'control')).toEqual(true)
+      expect(group.hasError('required', 'group.control')).toEqual(false)
+
+      group.setValue({ control: 'test' })
+      await flushPromises()
+
+      expect(group.invalid.value).toEqual(false)
+      expect(group.hasError('test')).toEqual(false)
+      expect(group.hasError('required', 'control')).toEqual(false)
+      expect(group.hasError('required', 'group.control')).toEqual(false)
+
+      await group.validate()
+
+      expect(group.invalid.value).toEqual(true)
+      expect(group.hasError('required', 'group.control')).toEqual(true)
+    })
+
+    test('submit trigger validate work', async () => {
+      const _asyncValidator = (_: unknown) => Promise.resolve({ async: { message: 'async' } } as ValidationErrors)
+
+      group = new FormGroup<BasicGroup>(
+        {
+          control: new FormControl('', Validators.required),
+          array: new FormArray([new FormControl(''), new FormControl()]),
+          group: new FormGroup({
+            control: new FormControl(null, { asyncValidators: _asyncValidator }),
+          }),
+        },
+        { trigger: 'submit', validators: _validator },
+      )
+
+      group.setValue({ control: '' })
+      group.markAsBlurred()
+      await flushPromises()
+
+      expect(group.invalid.value).toEqual(false)
+      expect(group.hasError('test')).toEqual(false)
+      expect(group.hasError('required', 'control')).toEqual(false)
+      expect(group.hasError('async', 'group.control')).toEqual(false)
+
+      await group.validate()
+
+      expect(group.invalid.value).toEqual(true)
+      expect(group.hasError('test')).toEqual(true)
+      expect(group.hasError('required', 'control')).toEqual(true)
+      expect(group.hasError('async', 'group.control')).toEqual(true)
+
+      group.setValue({ control: 'test' })
+      await flushPromises()
+
+      expect(group.invalid.value).toEqual(true)
+      expect(group.hasError('test')).toEqual(true)
+      expect(group.hasError('required', 'control')).toEqual(true)
+      expect(group.hasError('async', 'group.control')).toEqual(true)
+
+      await group.validate()
+
+      expect(group.invalid.value).toEqual(true)
+      expect(group.hasError('test')).toEqual(false)
+      expect(group.hasError('required', 'control')).toEqual(false)
+      expect(group.hasError('async', 'group.control')).toEqual(true)
+    })
+  })
+})
diff --git a/packages/cdk/forms/__tests__/typeof.spec.ts b/packages/cdk/forms/__tests__/typeof.spec.ts
index 99402f0d..65d43e19 100644
--- a/packages/cdk/forms/__tests__/typeof.spec.ts
+++ b/packages/cdk/forms/__tests__/typeof.spec.ts
@@ -1,12 +1,55 @@
-import { isFormControl } from '../src/typeof'
-import { ModelType } from '../src/constant'
-import { useFormControl } from '../src/useFormControl'
+import { FormArray } from '../src/controls/formArray'
+import { FormControl } from '../src/controls/formControl'
+import { FormGroup } from '../src/controls/formGroup'
+import { isAbstractControl, isFormArray, isFormControl, isFormGroup } from '../src/typeof'
+import { useFormArray, useFormControl, useFormGroup } from '../src/useForms'
 
 describe('typeof.ts', () => {
+  test('isAbstractControl work', () => {
+    expect(isAbstractControl(new FormGroup({}))).toEqual(true)
+    expect(isAbstractControl(useFormGroup({}))).toEqual(true)
+    expect(isAbstractControl(new FormControl())).toEqual(true)
+    expect(isAbstractControl(useFormControl())).toEqual(true)
+    expect(isAbstractControl(new FormArray([]))).toEqual(true)
+    expect(isAbstractControl(useFormArray([]))).toEqual(true)
+
+    expect(isAbstractControl(null)).toEqual(false)
+    expect(isAbstractControl({})).toEqual(false)
+  })
+
+  test('isFormGroup work', () => {
+    expect(isFormGroup(new FormGroup({}))).toEqual(true)
+    expect(isFormGroup(useFormGroup({}))).toEqual(true)
+
+    expect(isFormGroup(new FormControl())).toEqual(false)
+    expect(isFormGroup(useFormControl())).toEqual(false)
+    expect(isFormGroup(new FormArray([]))).toEqual(false)
+    expect(isFormGroup(useFormArray([]))).toEqual(false)
+    expect(isFormGroup(null)).toEqual(false)
+    expect(isFormGroup({})).toEqual(false)
+  })
+
   test('isFormControl work', () => {
+    expect(isFormControl(new FormControl())).toEqual(true)
+    expect(isFormControl(useFormControl())).toEqual(true)
+
+    expect(isFormControl(new FormGroup({}))).toEqual(false)
+    expect(isFormControl(useFormGroup({}))).toEqual(false)
+    expect(isFormControl(new FormArray([]))).toEqual(false)
+    expect(isFormControl(useFormArray([]))).toEqual(false)
+    expect(isFormControl(null)).toEqual(false)
+    expect(isFormControl({})).toEqual(false)
+  })
+
+  test('isFormArray work', () => {
+    expect(isFormArray(new FormArray([]))).toEqual(true)
+    expect(isFormArray(useFormArray([]))).toEqual(true)
+
+    expect(isFormArray(new FormGroup({}))).toEqual(false)
+    expect(isFormArray(useFormGroup({}))).toEqual(false)
+    expect(isFormArray(new FormControl())).toEqual(false)
+    expect(isFormArray(useFormControl())).toEqual(false)
     expect(isFormControl(null)).toEqual(false)
     expect(isFormControl({})).toEqual(false)
-    expect(isFormControl({ __type: ModelType.Control })).toEqual(true)
-    expect(isFormControl(useFormControl())).toEqual(true)
   })
 })
diff --git a/packages/cdk/forms/__tests__/useFormControl.spec.ts b/packages/cdk/forms/__tests__/useFormControl.spec.ts
deleted file mode 100644
index 8b6db473..00000000
--- a/packages/cdk/forms/__tests__/useFormControl.spec.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { flushPromises } from '@vue/test-utils'
-
-import { AsyncValidatorFn, Errors } from '../src/types'
-import { FormControl, useFormControl } from '../src/useFormControl'
-import { Validators } from '../src/validators'
-
-describe('useFormControl.ts', () => {
-  describe('basic work', () => {
-    let control: FormControl
-
-    beforeEach(() => {
-      control = useFormControl()
-    })
-
-    test('init modelRef work', () => {
-      expect(control.modelRef.value).toBeNull()
-    })
-
-    test('init status work', () => {
-      expect(control.status.value).toEqual('valid')
-      expect(control.valid.value).toEqual(true)
-      expect(control.invalid.value).toEqual(false)
-      expect(control.validating.value).toEqual(false)
-    })
-
-    test('init errors work', () => {
-      expect(control.errors.value).toBeNull()
-    })
-
-    test('init blurred work', () => {
-      expect(control.blurred.value).toEqual(false)
-      expect(control.unblurred.value).toEqual(true)
-    })
-
-    test('validate work', async () => {
-      expect(await control.validate()).toBeNull()
-    })
-
-    test('reset work', async () => {
-      control.setValue('test')
-      control.markAsBlurred()
-
-      expect(control.modelRef.value).toEqual('test')
-      expect(control.blurred.value).toEqual(true)
-
-      control.reset()
-
-      expect(control.modelRef.value).toBeNull()
-      expect(control.blurred.value).toEqual(false)
-    })
-
-    test('setValue work', async () => {
-      expect(control.modelRef.value).toBeNull()
-
-      control.setValue('test')
-
-      expect(control.modelRef.value).toEqual('test')
-
-      control.setValue('')
-
-      expect(control.modelRef.value).toEqual('')
-    })
-
-    test('setValidator work', async () => {
-      const { required, minLength, email } = Validators
-      control.setValidator(required)
-
-      expect(await control.validate()).toEqual({ required: { message: '' } })
-
-      control.setValidator([email, minLength(5)])
-      control.setValue('test')
-
-      expect(await control.validate()).toEqual({
-        email: { message: '', actual: 'test' },
-        minLength: { message: '', minLength: 5, actual: 4 },
-      })
-    })
-
-    test('setAsyncValidator work', async () => {
-      const _asyncValidator = (key: string, error: unknown): AsyncValidatorFn => {
-        return (_: unknown) => {
-          return Promise.resolve({ [key]: error } as Errors)
-        }
-      }
-      const message1 = { message: 1 }
-      const message2 = { message: 2 }
-
-      control.setAsyncValidator(_asyncValidator('a', message1))
-
-      expect(await control.validate()).toEqual({ a: message1 })
-
-      control.setAsyncValidator([_asyncValidator('a', message1), _asyncValidator('b', message2)])
-
-      expect(await control.validate()).toEqual({ a: message1, b: message2 })
-    })
-
-    test('setErrors work', async () => {
-      expect(control.errors.value).toBeNull()
-      expect(control.getError('required')).toBeNull()
-
-      const errors = { required: { message: '' } }
-      control.setErrors(errors)
-
-      expect(control.errors.value).toEqual(errors)
-
-      expect(control.getError('required')).toEqual(errors.required)
-      expect(control.getError('max')).toBeNull()
-
-      expect(control.hasError('required')).toEqual(true)
-      expect(control.hasError('max')).toEqual(false)
-    })
-  })
-
-  describe('trigger work', () => {
-    let control: FormControl
-
-    test('default change work', async () => {
-      control = useFormControl(null, { validators: Validators.required })
-
-      control.setValue('')
-      await flushPromises()
-
-      expect(control.errors.value).toEqual({ required: { message: '' } })
-    })
-
-    test('blur trigger validate work', async () => {
-      control = useFormControl(null, { trigger: 'blur', validators: Validators.required })
-      control.markAsBlurred()
-      await flushPromises()
-      expect(control.errors.value).toEqual({ required: { message: '' } })
-    })
-  })
-
-  describe('validator work', () => {
-    let control: FormControl
-
-    test('validate work', async () => {
-      control = useFormControl(null, [Validators.required])
-
-      control.setValue('test')
-      await flushPromises()
-      expect(control.errors.value).toBeNull()
-
-      control.setValue('')
-      await flushPromises()
-      expect(control.errors.value).toEqual({ required: { message: '' } })
-    })
-  })
-})
diff --git a/packages/cdk/forms/__tests__/useForms.spec.ts b/packages/cdk/forms/__tests__/useForms.spec.ts
new file mode 100644
index 00000000..99f892b8
--- /dev/null
+++ b/packages/cdk/forms/__tests__/useForms.spec.ts
@@ -0,0 +1,45 @@
+import { flushPromises } from '@vue/test-utils'
+import { useFormArray, useFormControl, useFormGroup } from '../src/useForms'
+import { Validators } from '../src/validators'
+
+interface BasicGroup {
+  control1: string
+  control2: number
+  array: string[]
+  group: {
+    control: string
+  }
+}
+
+const basicValue = { control1: null, control2: '', array: ['', null], group: { control: null } }
+
+describe('useForms.ts', () => {
+  test('basic work', async () => {
+    const group = useFormGroup<BasicGroup>({
+      control1: [null, Validators.required],
+      control2: ['', { trigger: 'blur', validators: Validators.required }],
+      array: useFormArray(['', null]),
+      group: useFormGroup({
+        control: useFormControl(),
+      }),
+    })
+
+    expect(group.getValue()).toEqual(basicValue)
+    expect(group.invalid.value).toEqual(false)
+    expect(group.hasError('required', 'control1')).toEqual(false)
+    expect(group.hasError('required', 'control2')).toEqual(false)
+
+    group.setValue({ control1: '' })
+    await flushPromises()
+
+    expect(group.getValue()).toEqual({ ...basicValue, control1: '' })
+    expect(group.invalid.value).toEqual(true)
+    expect(group.hasError('required', 'control1')).toEqual(true)
+    expect(group.hasError('required', 'control2')).toEqual(false)
+
+    group.markAsBlurred()
+    await flushPromises()
+
+    expect(group.hasError('required', 'control2')).toEqual(true)
+  })
+})
diff --git a/packages/cdk/forms/__tests__/validators.spec.ts b/packages/cdk/forms/__tests__/validators.spec.ts
index bb7b2d87..c27ee42c 100644
--- a/packages/cdk/forms/__tests__/validators.spec.ts
+++ b/packages/cdk/forms/__tests__/validators.spec.ts
@@ -1,153 +1,156 @@
 /* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { AsyncValidatorFn, ErrorMessages, Errors, ValidatorFn } from '../src/types'
+import { AbstractControl } from '../src/controls/abstractControl'
+import { AsyncValidatorFn, ErrorMessages, ValidationErrors, ValidatorFn } from '../src/types'
 import { Validators } from '../src/validators'
 
 describe('validators.ts', () => {
+  const control = (null as unknown) as AbstractControl
+
   test('required work', () => {
     const required = Validators.required
 
-    expect(required(0)).toBeNull()
-    expect(required('value')).toBeNull()
-    expect(required([1, 2])).toBeNull()
+    expect(required(0, control)).toBeNull()
+    expect(required('value', control)).toBeNull()
+    expect(required([1, 2], control)).toBeNull()
 
     const errorMessage = { required: { message: '' } }
-    expect(required(null)).toEqual(errorMessage)
-    expect(required(undefined)).toEqual(errorMessage)
-    expect(required('')).toEqual(errorMessage)
-    expect(required([])).toEqual(errorMessage)
+    expect(required(null, control)).toEqual(errorMessage)
+    expect(required(undefined, control)).toEqual(errorMessage)
+    expect(required('', control)).toEqual(errorMessage)
+    expect(required([], control)).toEqual(errorMessage)
   })
 
   test('requiredTrue work', () => {
     const requiredTrue = Validators.requiredTrue
 
-    expect(requiredTrue(true)).toBeNull()
+    expect(requiredTrue(true, control)).toBeNull()
 
     const errorMessage = (actual: unknown) => ({ requiredTrue: { message: '', actual } })
-    expect(requiredTrue(null)).toEqual(errorMessage(null))
-    expect(requiredTrue(undefined)).toEqual(errorMessage(undefined))
-    expect(requiredTrue('')).toEqual(errorMessage(''))
-    expect(requiredTrue([])).toEqual(errorMessage([]))
-    expect(requiredTrue({})).toEqual(errorMessage({}))
-    expect(requiredTrue(false)).toEqual(errorMessage(false))
+    expect(requiredTrue(null, control)).toEqual(errorMessage(null))
+    expect(requiredTrue(undefined, control)).toEqual(errorMessage(undefined))
+    expect(requiredTrue('', control)).toEqual(errorMessage(''))
+    expect(requiredTrue([], control)).toEqual(errorMessage([]))
+    expect(requiredTrue({}, control)).toEqual(errorMessage({}))
+    expect(requiredTrue(false, control)).toEqual(errorMessage(false))
   })
 
   test('email work', () => {
     const email = Validators.email
 
-    expect(email('')).toBeNull()
-    expect(email(null)).toBeNull()
-    expect(email('test@gmail.com')).toBeNull()
+    expect(email('', control)).toBeNull()
+    expect(email(null, control)).toBeNull()
+    expect(email('test@gmail.com', control)).toBeNull()
 
     const errorMessage = (actual: unknown) => ({ email: { message: '', actual } })
-    expect(email({})).toEqual(errorMessage({}))
-    expect(email('test')).toEqual(errorMessage('test'))
+    expect(email({}, control)).toEqual(errorMessage({}))
+    expect(email('test', control)).toEqual(errorMessage('test'))
   })
 
   test('min work', () => {
     const minOne = Validators.min(1)
 
-    expect(minOne('')).toBeNull()
-    expect(minOne(null)).toBeNull()
-    expect(minOne('test')).toBeNull()
-    expect(minOne('1')).toBeNull()
-    expect(minOne(1)).toBeNull()
-    expect(minOne(2)).toBeNull()
+    expect(minOne('', control)).toBeNull()
+    expect(minOne(null, control)).toBeNull()
+    expect(minOne('test', control)).toBeNull()
+    expect(minOne('1', control)).toBeNull()
+    expect(minOne(1, control)).toBeNull()
+    expect(minOne(2, control)).toBeNull()
 
     const errorMessage = (actual: unknown) => ({ min: { message: '', min: 1, actual } })
-    expect(minOne(0)).toEqual(errorMessage(0))
-    expect(minOne('0')).toEqual(errorMessage('0'))
+    expect(minOne(0, control)).toEqual(errorMessage(0))
+    expect(minOne('0', control)).toEqual(errorMessage('0'))
   })
 
   test('max work', () => {
     const maxOne = Validators.max(1)
 
-    expect(maxOne('')).toBeNull()
-    expect(maxOne(null)).toBeNull()
-    expect(maxOne('test')).toBeNull()
-    expect(maxOne('1')).toBeNull()
-    expect(maxOne(1)).toBeNull()
-    expect(maxOne(0)).toBeNull()
+    expect(maxOne('', control)).toBeNull()
+    expect(maxOne(null, control)).toBeNull()
+    expect(maxOne('test', control)).toBeNull()
+    expect(maxOne('1', control)).toBeNull()
+    expect(maxOne(1, control)).toBeNull()
+    expect(maxOne(0, control)).toBeNull()
 
     const errorMessage = (actual: unknown) => ({ max: { message: '', max: 1, actual } })
-    expect(maxOne(2)).toEqual(errorMessage(2))
-    expect(maxOne('2')).toEqual(errorMessage('2'))
+    expect(maxOne(2, control)).toEqual(errorMessage(2))
+    expect(maxOne('2', control)).toEqual(errorMessage('2'))
   })
 
   test('minLength work', () => {
     const minLengthTwo = Validators.minLength(2)
 
-    expect(minLengthTwo('')).toBeNull()
-    expect(minLengthTwo(null)).toBeNull()
-    expect(minLengthTwo(1)).toBeNull()
-    expect(minLengthTwo('te')).toBeNull()
-    expect(minLengthTwo('test')).toBeNull()
-    expect(minLengthTwo([])).toBeNull()
-    expect(minLengthTwo([1, 2])).toBeNull()
-    expect(minLengthTwo([1, 2, 3])).toBeNull()
+    expect(minLengthTwo('', control)).toBeNull()
+    expect(minLengthTwo(null, control)).toBeNull()
+    expect(minLengthTwo(1, control)).toBeNull()
+    expect(minLengthTwo('te', control)).toBeNull()
+    expect(minLengthTwo('test', control)).toBeNull()
+    expect(minLengthTwo([], control)).toBeNull()
+    expect(minLengthTwo([1, 2], control)).toBeNull()
+    expect(minLengthTwo([1, 2, 3], control)).toBeNull()
 
     const errorMessage = (actual: unknown) => ({ minLength: { message: '', minLength: 2, actual } })
-    expect(minLengthTwo('t')).toEqual(errorMessage(1))
-    expect(minLengthTwo([1])).toEqual(errorMessage(1))
+    expect(minLengthTwo('t', control)).toEqual(errorMessage(1))
+    expect(minLengthTwo([1], control)).toEqual(errorMessage(1))
   })
 
   test('maxLength work', () => {
     const maxLengthTwo = Validators.maxLength(2)
 
-    expect(maxLengthTwo('')).toBeNull()
-    expect(maxLengthTwo(1)).toBeNull()
-    expect(maxLengthTwo(null)).toBeNull()
-    expect(maxLengthTwo('te')).toBeNull()
-    expect(maxLengthTwo('t')).toBeNull()
-    expect(maxLengthTwo([])).toBeNull()
-    expect(maxLengthTwo([1, 2])).toBeNull()
-    expect(maxLengthTwo([1])).toBeNull()
+    expect(maxLengthTwo('', control)).toBeNull()
+    expect(maxLengthTwo(1, control)).toBeNull()
+    expect(maxLengthTwo(null, control)).toBeNull()
+    expect(maxLengthTwo('te', control)).toBeNull()
+    expect(maxLengthTwo('t', control)).toBeNull()
+    expect(maxLengthTwo([], control)).toBeNull()
+    expect(maxLengthTwo([1, 2], control)).toBeNull()
+    expect(maxLengthTwo([1], control)).toBeNull()
 
     const errorMessage = (actual: unknown) => ({ maxLength: { message: '', maxLength: 2, actual } })
-    expect(maxLengthTwo('test')).toEqual(errorMessage(4))
-    expect(maxLengthTwo([1, 2, 3])).toEqual(errorMessage(3))
+    expect(maxLengthTwo('test', control)).toEqual(errorMessage(4))
+    expect(maxLengthTwo([1, 2, 3], control)).toEqual(errorMessage(3))
   })
 
   test('pattern work', () => {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    expect(Validators.pattern(null!)('test')).toBeNull()
+    expect(Validators.pattern(null!)('test', control)).toBeNull()
 
     let stringPattern = Validators.pattern('[a-zA-Z]+')
 
-    expect(stringPattern('')).toBeNull()
-    expect(stringPattern(null)).toBeNull()
-    expect(stringPattern('test')).toBeNull()
+    expect(stringPattern('', control)).toBeNull()
+    expect(stringPattern(null, control)).toBeNull()
+    expect(stringPattern('test', control)).toBeNull()
 
     let errorMessage = (actual: unknown) => ({ pattern: { message: '', pattern: '^[a-zA-Z]+$', actual } })
-    expect(stringPattern('test1')).toEqual(errorMessage('test1'))
-    expect(stringPattern(1)).toEqual(errorMessage(1))
+    expect(stringPattern('test1', control)).toEqual(errorMessage('test1'))
+    expect(stringPattern(1, control)).toEqual(errorMessage(1))
 
     stringPattern = Validators.pattern('^[a-zA-Z]+$')
-    expect(stringPattern('test1')).toEqual(errorMessage('test1'))
-    expect(stringPattern(1)).toEqual(errorMessage(1))
+    expect(stringPattern('test1', control)).toEqual(errorMessage('test1'))
+    expect(stringPattern(1, control)).toEqual(errorMessage(1))
 
     const regExpPattern = Validators.pattern(new RegExp('[a-zA-Z]+'))
 
-    expect(regExpPattern('')).toBeNull()
-    expect(regExpPattern(null)).toBeNull()
-    expect(regExpPattern('test')).toBeNull()
-    expect(regExpPattern('test1')).toBeNull()
+    expect(regExpPattern('', control)).toBeNull()
+    expect(regExpPattern(null, control)).toBeNull()
+    expect(regExpPattern('test', control)).toBeNull()
+    expect(regExpPattern('test1', control)).toBeNull()
 
     errorMessage = (actual: unknown) => ({ pattern: { message: '', pattern: '/[a-zA-Z]+/', actual } })
-    expect(regExpPattern(1)).toEqual(errorMessage(1))
+    expect(regExpPattern(1, control)).toEqual(errorMessage(1))
 
     const regExpPattern2 = Validators.pattern(new RegExp('^[a-zA-Z]+$'))
 
-    expect(regExpPattern2('test')).toBeNull()
+    expect(regExpPattern2('test', control)).toBeNull()
 
     errorMessage = (actual: unknown) => ({ pattern: { message: '', pattern: '/^[a-zA-Z]+$/', actual } })
-    expect(regExpPattern2('test1')).toEqual(errorMessage('test1'))
-    expect(regExpPattern2(1)).toEqual(errorMessage(1))
+    expect(regExpPattern2('test1', control)).toEqual(errorMessage('test1'))
+    expect(regExpPattern2(1, control)).toEqual(errorMessage(1))
   })
 
   test('compose work', () => {
     const _validator = (key: string, error: unknown): ValidatorFn => {
       return (_: unknown) => {
-        return { [key]: error } as Errors
+        return { [key]: error } as ValidationErrors
       }
     }
     const message1 = { message: 1 }
@@ -156,20 +159,20 @@ describe('validators.ts', () => {
 
     expect(compose(null)).toBeNull()
     expect(compose([])).toBe(null)
-    expect(compose([nullValidator, nullValidator])!('test')).toBeNull()
+    expect(compose([nullValidator, nullValidator])!('test', control)).toBeNull()
 
-    expect(compose([_validator('a', message1), _validator('b', message2)])!('test')).toEqual({
+    expect(compose([_validator('a', message1), _validator('b', message2)])!('test', control)).toEqual({
       a: message1,
       b: message2,
     })
-    expect(compose([_validator('a', message1), _validator('a', message2)])!('test')).toEqual({ a: message2 })
-    expect(compose([null, nullValidator, required])!('')).toEqual({ required: { message: '' } })
+    expect(compose([_validator('a', message1), _validator('a', message2)])!('test', control)).toEqual({ a: message2 })
+    expect(compose([null, nullValidator, required])!('', control)).toEqual({ required: { message: '' } })
   })
 
   test('composeAsync work', async () => {
     const _asyncValidator = (key: string, error: unknown): AsyncValidatorFn => {
       return (_: unknown) => {
-        return Promise.resolve({ [key]: error } as Errors)
+        return Promise.resolve({ [key]: error } as ValidationErrors)
       }
     }
     const message1 = { message: 1 }
@@ -179,14 +182,14 @@ describe('validators.ts', () => {
     expect(composeAsync(null)).toBe(null)
     expect(composeAsync([])).toBe(null)
 
-    let errors = await composeAsync([_asyncValidator('a', message1), _asyncValidator('b', message2)])!('test')
+    let errors = await composeAsync([_asyncValidator('a', message1), _asyncValidator('b', message2)])!('test', control)
 
     expect(errors).toEqual({ a: message1, b: message2 })
 
-    errors = await composeAsync([_asyncValidator('a', message1), _asyncValidator('a', message2)])!('test')
+    errors = await composeAsync([_asyncValidator('a', message1), _asyncValidator('a', message2)])!('test', control)
     expect(errors).toEqual({ a: message2 })
 
-    errors = await composeAsync([null, _asyncValidator('a', message1)])!('test')
+    errors = await composeAsync([null, _asyncValidator('a', message1)])!('test', control)
     expect(errors).toEqual({ a: message1 })
   })
 
@@ -196,11 +199,11 @@ describe('validators.ts', () => {
     const messages: ErrorMessages = { default: 'invalid input', required: 'please input' }
     setMessages(messages)
 
-    expect(required('')).toEqual({ required: { message: messages.required } })
-    expect(requiredTrue(false)).toEqual({ requiredTrue: { message: messages.default, actual: false } })
+    expect(required('', control)).toEqual({ required: { message: messages.required } })
+    expect(requiredTrue(false, control)).toEqual({ requiredTrue: { message: messages.default, actual: false } })
 
     setMessages({ requiredTrue: () => 'please input true' })
 
-    expect(requiredTrue(false)).toEqual({ requiredTrue: { message: 'please input true', actual: false } })
+    expect(requiredTrue(false, control)).toEqual({ requiredTrue: { message: 'please input true', actual: false } })
   })
 })
diff --git a/packages/cdk/utils/__tests__/convert.spec.ts b/packages/cdk/utils/__tests__/convert.spec.ts
index 9e5b0337..3afe884d 100644
--- a/packages/cdk/utils/__tests__/convert.spec.ts
+++ b/packages/cdk/utils/__tests__/convert.spec.ts
@@ -2,6 +2,9 @@ import { toArray, toNumber, toBoolean, toCssPixel } from '../convert'
 
 describe('convert.ts', () => {
   test('toArray work', async () => {
+    expect(toArray(null)).toEqual([])
+    expect(toArray(undefined)).toEqual([])
+
     const stringValue = 'string'
     expect(toArray(stringValue)).toEqual([stringValue])