-
-
Save vijayksingh/09fc69da105c86c01ae728ca64205b9d to your computer and use it in GitHub Desktop.
Typed @angular/forms FIXED set/patchValue - strict all the way down ^_^
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { FormGroup, FormControl, FormArray, Validators } from "@angular/forms"; | |
function testFormGroupTyped() { | |
var frm = new FormGroup({ | |
a: new FormArray([new FormControl(0)]), | |
b: new FormControl(true), | |
c: new FormGroup({ | |
s: new FormControl("abc"), | |
n: new FormControl(123) | |
}) | |
//d: new FormControl(null) | |
}) as FormGroupTyped<{ a: number[]; b: boolean; c: { s: string; n: number } /*; d?: {}*/ }>; | |
frm.status; | |
var x = frm.controls["a"].value; //OK infer number[] | |
var y = frm.controls.b.value; //OK infer boolean | |
frm.controls["c"].valueChanges.subscribe(z => console.log(z)); //OK infer {s: string, n:number} | |
var c = frm.get("c").value; //OK infer {s: string, n:number} | |
var s = frm.get(["c", "s"]).value; //OK infer unknown (Q: any is BETTER?!) | |
var u = frm.get("u").value; //OK infer unknow (Q: any is BETTER?!) | |
var r = frm.getRawValue(); //OK infer T + allow extra props | |
var a = r.a; //OK infer number[] | |
var d = r.d; //OK infer any | |
var ok = frm.contains("a"); //OK infer boolean | |
var ko = frm.contains("d"); //OK infer boolean -> true=enabled, false=disabled | |
frm.setValue({ a: [1, 23], b: true, c: { s: "s", n: 1 } }); // OK | |
frm.setValue({ b: false }, { onlySelf: true, emitEvent: false }); //ERROR -> setValue constraint to full T | |
frm.patchValue({ b: true }, { onlySelf: true, emitEvent: false }); //OK -> patchValue accept Partial<T> | |
frm.patchValue({ bb: true }, { onlySelf: true, emitEvent: false }); //ERROR -> not valid type Partial<T> | |
frm.removeControl("a"); //OK with auto-complete "a" | |
frm.removeControl("d"); //OK | |
frm.registerControl("a", new FormControl(null)); | |
frm.registerControl("d", new FormControl(null)); //OK method with correct signature :-) | |
frm.get("a").setValue([1, 2]); //OK - valid constranint check number[] | |
frm.get("b").setValue("true"); //ERROR - invalid value boolean expected | |
frm.get("d").setValue({ z: "xy" }); //OK - any evrything is a valid assignment | |
var z: string = frm.get("d").value.z; //ERROR - but reading unknown force you to check before access | |
frm.get("b").valueChanges.subscribe(b => console.log(b)); //OK - infer boolean correctly without cast | |
frm.get("d").valueChanges.subscribe((u: { z: string }) => console.log(u.z)); //OK - explicit type cast to read from unknown | |
frm.get("d").value; //OK - anything is valid | |
frm.get("d").disable(); //OK method with correct signature :-) | |
frm.removeControl("b"); //OK with auto-complete "b" :-) | |
frm.removeControl("d"); //OK | |
frm.setControl("b", testFormControlTyped()); //OK method with strict signature :-) | |
frm.setControl("d", new FormControl(null)); //OK | |
frm.addControl("b", testFormControlTyped()); //OK | |
frm.addControl("a", testFormControlTyped()); //OK even if ovverride types :-() | |
frm.addControl("e", new FormControl(null)); //OK | |
frm.get(["e"]).reset(null, { onlySelf: true }); //OK method with correct signature :-) | |
} | |
function testFormArrayTyped() { | |
var arr = new FormArray([ | |
new FormGroup({ | |
s: new FormControl("abc"), | |
n: new FormControl(123) | |
}) | |
]) as FormArrayTyped<{ s: string; n: number }>; | |
var x = arr.controls[0].value; //OK infer {s: string, n:number} | |
var y = arr.at(1).value; //OK infer {s: string; n:number} | |
arr.controls[0].valueChanges.subscribe(z => console.log(z)); //OK infer {s: string, n:number} | |
arr.push(itemFormGroup()); //OK method with strict signature :-) | |
arr.push(new FormControl(null)); //OK allowed by Angular -> infer AbstractControlTyped<any> | |
arr.insert(0, itemFormGroup()); //OK method with strict signature :-) | |
arr.setControl(2, new FormGroup({})); //OK allowed by Angular -> infer AbstractControlTyped<any> | |
arr.setControl(1, itemFormGroup()); //OK method with strict signature :-) | |
arr.removeAt(0); //OK | |
var l = arr.length; //OK | |
arr.setValue([{ s: "a", n: 1 }, { s: "b", n: 2 }]); //OK | |
arr.setValue([{ s: "a", n: 1 }, { n: 2 }]); //ERROR -> setValue constraint to full T[] | |
arr.patchValue([{ s: "a" }, { n: 2 }, {}]); //OK -> patchValue accept Partial<T> | |
arr.patchValue([{ s: "a" }, { n: "NOP" }]); //ERROR -> not valid type Partial<T> | |
arr.setValue([1, 23]); //ERROR -> invalid item type | |
arr.setValue(1.23); //ERROR -> invalid type :-) | |
} | |
function testFormControlTyped() { | |
var ctrl = new FormControl(true) as FormControlTyped<number>; | |
var x = ctrl.value; //OK infer boolean | |
ctrl.valueChanges.subscribe(z => console.log(z)); //infer boolean | |
ctrl.setValue("abc"); //ERROR invalid type | |
ctrl.patchValue(false); //OK correct type | |
return ctrl; | |
} | |
function itemFormGroup() { | |
var ctrl = new FormGroup({ | |
s: new FormControl("", Validators.required), | |
n: new FormControl(0, [Validators.min(0), Validators.max(9)]) | |
}); | |
return ctrl as FormGroupTyped<{ s: string; n: number }>; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//BASIC TYPES DEFINED IN @angular/forms + rxjs/Observable | |
type FormGroup = import("@angular/forms").FormGroup; | |
type FormArray = import("@angular/forms").FormArray; | |
type FormControl = import("@angular/forms").FormControl; | |
type AbstractControl = import("@angular/forms").AbstractControl; | |
type Observable<T> = import("rxjs").Observable<T>; | |
type STATUS = "VALID" | "INVALID" | "PENDING" | "DISABLED"; //<- I don't know why Angular Team doesn't define it https://github.com/angular/angular/blob/7.2.7/packages/forms/src/model.ts#L15-L45) | |
type STATUSs = STATUS | string; //<- string is added only becouse Angular base class use string insted of union type https://github.com/angular/angular/blob/7.2.7/packages/forms/src/model.ts#L196) | |
//OVVERRIDE TYPES WITH STRICT TYPED INTERFACES + SOME TYPE TRICKS TO COMPOSE INTERFACE (https://github.com/Microsoft/TypeScript/issues/16936) | |
interface AbstractControlTyped<T> extends AbstractControl { | |
// BASE PROPS AND METHODS COMMON TO ALL FormControl/FormGroup/FormArray | |
readonly value: T; | |
valueChanges: Observable<T>; | |
readonly status: STATUSs; | |
statusChanges: Observable<STATUS>; | |
get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null; | |
setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
} | |
interface FormControlTyped<T> extends FormControl { | |
// COPIED FROM AbstractControlTyped<T> BECOUSE TS NOT SUPPORT MULPILE extends FormControl, AbstractControlTyped<T> | |
readonly value: T; | |
valueChanges: Observable<T>; | |
readonly status: STATUSs; | |
statusChanges: Observable<STATUS>; | |
get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null; | |
setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
} | |
interface FormGroupTyped<T> extends FormGroup { | |
// PROPS AND METHODS SPECIFIC OF FormGroup | |
//controls: { [P in keyof T | string]: AbstractControlTyped<P extends keyof T ? T[P] : any> }; | |
controls: { [P in keyof T]: AbstractControlTyped<T[P]> }; | |
registerControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): AbstractControlTyped<T[P]>; | |
registerControl<V = any>(name: string, control: AbstractControlTyped<V>): AbstractControlTyped<V>; | |
addControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): void; | |
addControl<V = any>(name: string, control: AbstractControlTyped<V>): void; | |
removeControl(name: keyof T): void; | |
removeControl(name: string): void; | |
setControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): void; | |
setControl<V = any>(name: string, control: AbstractControlTyped<V>): void; | |
contains(name: keyof T): boolean; | |
contains(name: string): boolean; | |
get<P extends keyof T>(path: P): AbstractControlTyped<T[P]>; | |
getRawValue(): T & { [disabledProp in string | number]: any }; | |
// COPIED FROM AbstractControlTyped<T> BECOUSE TS NOT SUPPORT MULPILE extends FormGroup, AbstractControlTyped<T> | |
readonly value: T; | |
valueChanges: Observable<T>; | |
readonly status: STATUSs; | |
statusChanges: Observable<STATUS>; | |
get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null; | |
setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
} | |
interface FormArrayTyped<T> extends FormArray { | |
// PROPS AND METHODS SPECIFIC OF FormGroup | |
controls: AbstractControlTyped<T>[]; | |
at(index: number): AbstractControlTyped<T>; | |
push<V = T>(ctrl: AbstractControlTyped<V>): void; | |
insert<V = T>(index: number, control: AbstractControlTyped<V>): void; | |
setControl<V = T>(index: number, control: AbstractControlTyped<V>): void; | |
getRawValue(): T[]; | |
// COPIED FROM AbstractControlTyped<T[]> BECOUSE TS NOT SUPPORT MULPILE extends FormArray, AbastractControlTyped<T[]> | |
readonly value: T[]; | |
valueChanges: Observable<T[]>; | |
readonly status: STATUSs; | |
statusChanges: Observable<STATUS>; | |
get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null; | |
setValue<V>(value: V extends T[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
patchValue<V>(value: V extends Partial<T>[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
reset<V>(value?: V extends Partial<T>[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment