Skip to content

Commit

Permalink
Merge pull request cloudnc#139 from ntziolis/master (typings)
Browse files Browse the repository at this point in the history
feat(ControlsType): added typings for AbstractControls + FormArrays
# Conflicts:
#	projects/ngx-sub-form/src/lib/ngx-sub-form-utils.ts
#	projects/ngx-sub-form/src/lib/ngx-sub-form.component.ts
#	projects/ngx-sub-form/src/lib/ngx-sub-form.types.ts
  • Loading branch information
ntziolis committed Jun 1, 2020
1 parent 9ae9ba1 commit 03a71fe
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 22 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ If you want to see the demo in action, please visit [https://cloudnc.github.io/n

- 2 classes for top level form components: `NgxRootFormComponent`, `NgxAutomaticRootFormComponent`
- 2 classes for sub level form components: `NgxSubFormComponent`, `NgxSubFormRemapComponent`
- 3 interfaces: `Controls<T>`, `ControlsNames<T>`, `FormGroupOptions<T>`
- 7 interfaces: `Controls<T>`, `ControlsNames<T>`, `FormGroupOptions<T>`, `TypedFormGroup<T>`, `TypedFormArray<T>`, `TypedFormControl<T>`, `TypedAbstractControl<T>`
- 1 function: `subformComponentProviders`

So there's actually nothing to setup (like a module), you can just use them directly.
Expand Down
2 changes: 1 addition & 1 deletion projects/ngx-sub-form/src/lib/ngx-root-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export abstract class NgxRootFormComponent<ControlInterface, FormInterface = Con
filter(newValue => !isEqual(newValue, this.formGroup.value)),
tap(newValue => {
if (!isNullOrUndefined(newValue)) {
this.formGroup.patchValue(newValue);
this.formGroup.patchValue(newValue, undefined);
}
}),
)
Expand Down
47 changes: 45 additions & 2 deletions projects/ngx-sub-form/src/lib/ngx-sub-form-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
NG_VALIDATORS,
ValidationErrors,
FormControl,
FormArray,
AbstractControl,
FormGroup,
} from '@angular/forms';
import { forwardRef, OnDestroy, Provider } from '@angular/core';
import { AbstractControl, FormArray, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors } from '@angular/forms';
import { Observable, Subject, timer } from 'rxjs';
import { debounce, takeUntil } from 'rxjs/operators';

Expand All @@ -12,7 +21,10 @@ export type ControlsNames<T> = { [K in keyof T]-?: K };

export type ControlMap<T, V> = { [K in keyof T]-?: V };

export type ControlsType<T> = { [K in keyof T]-?: T[K] extends any[] ? FormArray : AbstractControl };
export type ControlsType<T> = {
[K in keyof T]-?: T[K] extends any[] ? TypedFormArray<T[K]> : TypedFormControl<T[K]> | TypedFormGroup<T[K]>;
};

export type FormErrorsType<T> = {
[K in keyof T]-?: T[K] extends any[] ? (null | ValidationErrors)[] : ValidationErrors;
};
Expand All @@ -25,6 +37,37 @@ export type FormErrors<FormInterface> = null | Partial<
}
>;

// using set/patch value options signature from form controls to allow typing without additional casting
export interface TypedAbstractControl<TValue> extends AbstractControl {
value: TValue;
valueChanges: Observable<TValue>;
setValue(value: TValue, options?: Parameters<AbstractControl['setValue']>[1]): void;
patchValue(value: Partial<TValue>, options?: Parameters<AbstractControl['patchValue']>[1]): void;
}

export interface TypedFormGroup<TValue> extends FormGroup {
value: TValue;
valueChanges: Observable<TValue>;
controls: ControlsType<TValue>;
setValue(value: TValue, options?: Parameters<FormGroup['setValue']>[1]): void;
patchValue(value: Partial<TValue>, options?: Parameters<FormGroup['patchValue']>[1]): void;
}

export interface TypedFormArray<TValue extends any[]> extends FormArray {
value: TValue;
valueChanges: Observable<TValue>;
controls: TypedAbstractControl<TValue>[];
setValue(value: TValue, options?: Parameters<FormArray['setValue']>[1]): void;
patchValue(value: TValue, options?: Parameters<FormArray['patchValue']>[1]): void;
}

export interface TypedFormControl<TValue> extends FormGroup {
value: TValue;
valueChanges: Observable<TValue>;
setValue(value: TValue, options?: Parameters<FormControl['setValue']>[1]): void;
patchValue(value: Partial<TValue>, options?: Parameters<FormControl['patchValue']>[1]): void;
}

export type KeysWithType<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];

export type ArrayPropertyKey<T> = KeysWithType<T, Array<any>>;
Expand Down
22 changes: 11 additions & 11 deletions projects/ngx-sub-form/src/lib/ngx-sub-form.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Input, OnDestroy, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import {
AbstractControl,
AbstractControlOptions,
Expand All @@ -19,13 +19,15 @@ import {
ControlsType,
FormErrors,
isNullOrUndefined,
TypedAbstractControl,
TypedFormGroup,
} from './ngx-sub-form-utils';
import { FormGroupOptions, NgxFormWithArrayControls, TypedFormGroup } from './ngx-sub-form.types';
import { SubFormGroup, patchFormControl, SubFormArray } from './sub-form-group';
import { FormGroupOptions, NgxFormWithArrayControls, TypedSubFormGroup } from './ngx-sub-form.types';
import { patchFormControl, SubFormArray, SubFormGroup } from './sub-form-group';

type MapControlFunction<FormInterface, MapValue> = (ctrl: AbstractControl, key: keyof FormInterface) => MapValue;
type FilterControlFunction<FormInterface> = (
ctrl: AbstractControl,
ctrl: TypedAbstractControl<any>,
key: keyof FormInterface,
isCtrlWithinFormArray: boolean,
) => boolean;
Expand All @@ -39,7 +41,7 @@ export abstract class NgxSubFormComponent<ControlInterface, FormInterface = Cont
return null as any;
}

return this.formGroup.controls as ControlsType<FormInterface>;
return (this.formGroup.controls as unknown) as ControlsType<FormInterface>;
}

public get formGroupValues(): Required<FormInterface> {
Expand Down Expand Up @@ -78,7 +80,7 @@ export abstract class NgxSubFormComponent<ControlInterface, FormInterface = Cont
// see @note form-group-undefined

// tslint:disable-next-line: no-input-rename
@Input('subForm') formGroup!: TypedFormGroup<FormInterface>;
@Input('subForm') formGroup!: TypedSubFormGroup<ControlInterface, FormInterface>;// | SubFormArray<ControlInterface, FormInterface>;

protected emitNullOnDestroy = true;
protected emitInitialValueOnInit = true;
Expand All @@ -91,13 +93,11 @@ export abstract class NgxSubFormComponent<ControlInterface, FormInterface = Cont
return;
}

// provide a descriptive error message when the declaration the parent for was incorrect
if (!(this.formGroup instanceof SubFormGroup || this.formGroup instanceof SubFormArray)) {
// TODO rethink if this can ever be a sub form array
if (!(this.formGroup instanceof SubFormGroup)){// || this.formGroup instanceof SubFormArray)) {
throw new Error('The subForm input needs to be of type SubFormGroup.');
}

const oldControls = (this.formGroup.controls as unknown) as AbstractControl[];

Object.keys(this.formGroup.controls).forEach(key => {
this.formGroup.removeControl(key);
});
Expand Down Expand Up @@ -217,7 +217,7 @@ export abstract class NgxSubFormComponent<ControlInterface, FormInterface = Cont
return null;
}

const formControls: Controls<FormInterface> = this.formGroup.controls;
const formControls: ControlsType<FormInterface> = this.formGroup.controls;

const controls: Partial<ControlMap<FormInterface, MapValue | MapValue[]>> = {};

Expand Down
14 changes: 7 additions & 7 deletions projects/ngx-sub-form/src/lib/ngx-sub-form.types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import { FormControl, FormGroup, ValidationErrors } from '@angular/forms';
import { Observable } from 'rxjs';
import { ArrayPropertyKey, ArrayPropertyValue, Controls, FormUpdate } from './ngx-sub-form-utils';
import { ArrayPropertyKey, ArrayPropertyValue, Controls, FormUpdate, TypedFormGroup, ControlsType } from './ngx-sub-form-utils';
import { SubFormGroup } from './sub-form-group';

// @deprecated
export interface OnFormUpdate<FormInterface> {
// @deprecated
onFormUpdate?: (formUpdate: FormUpdate<FormInterface>) => void;
}

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
type Nullable<T> = T | null;

export type NullableObject<T> = { [P in keyof T]: Nullable<T[P]> };

export type TypedFormGroup<FormInterface> = Omit<FormGroup, 'controls' | 'value'> & {
controls: Controls<FormInterface>;
value: FormInterface;
};

export type TypedValidatorFn<T> = (formGroup: TypedFormGroup<T>) => ValidationErrors | null;

export type TypedAsyncValidatorFn<T> = (
Expand Down Expand Up @@ -47,3 +42,8 @@ export interface FormGroupOptions<T> {
export interface NgxFormWithArrayControls<T> {
createFormArrayControl(key: ArrayPropertyKey<T>, value: ArrayPropertyValue<T>): FormControl;
}


export interface TypedSubFormGroup<TControl, TForm = TControl> extends SubFormGroup<TControl, TForm> {
controls: ControlsType<TForm>;
}
2 changes: 2 additions & 0 deletions projects/ngx-sub-form/src/lib/sub-form-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ export class SubFormGroup<TControl, TForm = TControl> extends FormGroup {

const transformedValue = (this.transformToFormGroup((value as unknown) as TControl, {}) as unknown) as TForm;

// TODO rethink as this might not work as we want it, we might not even need this anymore
// @ts-ignore
(super.value as any) = transformedValue;

this.controlValue = value;
Expand Down

0 comments on commit 03a71fe

Please sign in to comment.