Skip to content

Commit

Permalink
fix(controlsOf): allow using optional fields
Browse files Browse the repository at this point in the history
Optional fields will only work with top-level values, and will not work with `FormGroup`:

```ts
interface User {
  name?: string;
  foo?: string[];
  // πŸ‘‡πŸ» will not work
  nested?: {
    id: string;
  };
}
```

Fixes #111
  • Loading branch information
NetanelBasal committed Oct 21, 2021
1 parent a2e8d48 commit 513beee
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 6 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ const profileForm = new FormGroup<ControlsOf<Profile>>({
})
});
```
#### Gotchas

Note that when using `array` types, it'll automatically infer it as `FormArray`. If you need a `FormControl`, you must set it within your interface explicitly:
- When using `array` types, it'll automatically infer it as `FormArray`. If you need a `FormControl`, you must set it within your interface explicitly:

```ts
interface User {
Expand All @@ -129,6 +130,19 @@ interface User {
}
```

- Optional fields will only work with top-level values, and will not work with `FormGroup`:

```ts
interface User {
name?: string;
foo?: string[];
// πŸ‘‡πŸ» will not work
nested?: {
id: string;
};
}
```

## Control Queries

### `value$`
Expand Down
33 changes: 33 additions & 0 deletions libs/reactive-forms/src/lib/form-group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,4 +560,37 @@ describe('ControlsOf', () => {

})


it('should work with optional fields', () => {
type Foo = {
name?: string;
foo: string;
baz: null | string;
arr?: string[];
nested: {
id: string
}
}

const group = new FormGroup<ControlsOf<Foo>>({
foo: new FormControl(''),
name: new FormControl(''),
baz: new FormControl(null),
arr: new FormArray([]),
nested: new FormGroup({
id: new FormControl('')
})
})

// @ts-expect-error - should be a string
group.get('name')?.patchValue(1);

expectTypeOf(group.get('name')).toEqualTypeOf<FormControl<string | undefined> | undefined>();

expectTypeOf(group.value.name).toEqualTypeOf<string | undefined>();
expectTypeOf(group.value.arr).toEqualTypeOf<string[] | undefined>();
expectTypeOf(group.value.baz).toEqualTypeOf<string | null>();
expectTypeOf(group.value.nested).toEqualTypeOf<{ id: string }>();
})

});
12 changes: 7 additions & 5 deletions libs/reactive-forms/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ import { FormControl } from './form-control';
import { FormArray } from './form-array';
import { AbstractControl } from '@angular/forms';

type NonUndefined<T> = T extends undefined ? never : T;

// This type is **Experimental**
export type ControlsOf<T extends Record<string, any>> = {
[K in keyof T]: T[K] extends AbstractControl ? T[K] : T[K] extends (infer R)[]
[K in keyof T]: NonUndefined<T[K]> extends AbstractControl ? T[K] : NonUndefined<T[K]> extends (infer R)[]
? FormArray<R>
: T[K] extends Record<any, any>
: NonUndefined<T[K]> extends Record<any, any>
? FormGroup<ControlsOf<T[K]>>
: FormControl<T[K]>;
};

export type ValuesOf<T extends ControlsOf<any>> = {
[K in keyof T]: T[K] extends FormControl<infer R>
[K in keyof T]: NonUndefined<T[K]> extends FormControl<infer R>
? R
: T[K] extends FormGroup<infer R>
: NonUndefined<T[K]> extends FormGroup<infer R>
? ValuesOf<R>
: T[K] extends FormArray<infer R> ? R[] : T[K];
: NonUndefined<T[K]> extends FormArray<infer R> ? R[] : NonUndefined<T[K]>;
};

export type DeepPartial<T> = {
Expand Down

0 comments on commit 513beee

Please sign in to comment.