Skip to content

Commit

Permalink
Breaking changes
Browse files Browse the repository at this point in the history
Make library behavior more predictable

InferModes has been removed
Code simplification
Fix tests
Fix examples
Fix README
  • Loading branch information
iamguid committed Oct 5, 2023
1 parent e7442b4 commit 50b7838
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 827 deletions.
58 changes: 3 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
# ngx-mf
`ngx-mf` is a small zero dependency set of TypeScript types for recursive
`ngx-mf` is a small (100 lines of code) zero dependency set of TypeScript types for recursive
infer angular `FormGroup`, `FormArray` or `FormControl` type
from model type.

It doesn't increase your bundle size because it's just
TypeScript types.

WARNING: I found some issues in TypeScript,
for workaround it I use @ts-ignore,
if it critical for your project don't use that library.
Because in future TypeScript releases library may break down

## Installation

npm
Expand Down Expand Up @@ -80,14 +75,13 @@ FormGroup<{

`ngx-mf` exports type `FormModel`

`FormModel<TModel, TAnnotations, TInferMode>` - This is the type
`FormModel<TModel, TAnnotations>` - This is the type
that recursively turns `TModel` fields (where `TModel` is your model type)
into a `FormGroup`, `FormArray` or `FormControl`.
You can choose what you want: `FormGroup`, `FormArray` or `FormControl`
by annotations.
You can pass `TAnnotations` as the second argument to specify
output type using special syntax.
Also you can pass `TInferType` for manage infer logic, see Infer Mode chapter.

For example we have some model from How It Works chapter:

Expand Down Expand Up @@ -118,17 +112,14 @@ should be `FormControl` and field `contacts` should be
`FormArray` of `FormGroups`.

First of all we need to exclude `id` from our model,
it is needed because all fields are required by default.
it is needed because all fields are required.
If we need to exclude some fields we
should omit or pick them from source model.

```typescript
Omit<IUserModel, 'id'>
```

Ofcourse you can save optional fields in output type,
see chapter Infer Mode

If we want to add some field then we should using `&`
operator or extends interface, for examle:

Expand Down Expand Up @@ -199,49 +190,6 @@ FormGroup<{

Also you can check [/tests/annotations.test.ts](https://github.com/iamguid/ngx-mf/blob/master/tests/annotations.test.ts)

## Infer Modes
`ngx-mf` have different InferMode-s and it's a third type parameter of `FormModel`,
InferMode needed for manage what you want to infer.
For example we need make all fields in form optional,
but we want that all controls should be nonnullable,
it is a case of InferMode.

So we need define FormModel like this:

```typescript
type Form = FormModel<IUserModel, { contacts: [FormElementGroup] }, InferModeOptional & InferModeNonNullable>;
```

Then we have that type:

```typescript
FormGroup<{
firstName?: FormControl<string>;
lastName?: FormControl<string>;
nickname?: FormControl<string>;
birthday?: FormControl<Date>;
contacts?: FormArray<FormGroup<{
type?: FormControl<ContactType>;
contact?: FormControl<string>;
}>>;
}>
```

As we see now FormGroups has optional fields and all FormControls is nonnullable

Available variants:
* `InferModeFromModel` - infer all from model (optionals and nullability)
* `InferModeFromModel & InferModeNonNullable` - infer optionals from model, and all controls will be all nonnullable
* `InferModeFromModel & InferModeNullable` - infer optionals from model, and all controls will be nullable
* `InferModeOptional & InferModeNonNullable` - makes all fields optional and all controls will be nonnullable
* `InferModeOptional & InferModeNullable` - makes all fields optional, and all controls will be nnullable
* `InferModeOptional & InferModeFromModel` - makes all fields optional, and controls infer nullability from model
* `InferModeRequired & InferModeNonNullable` - makes all fields required, and all controls will be nonnullable
* `InferModeRequired & InferModeNullable` - makes all fields required, and all controls will be nnullable
* `InferModeRequired & InferModeFromModel` - makes all fields required, and controls infer nullability from model

Also you can check [/tests/infermode.test.ts](https://github.com/iamguid/ngx-mf/blob/master/tests/infermode.test.ts)

## Examples Of Usage

> Definition of example model:
Expand Down
50 changes: 5 additions & 45 deletions dist/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @ts-nocheck
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
declare type DefaultInferMode = InferModeNullable & InferModeRequired;
export declare type FormModel<TModel, TAnnotations extends TransformToAnnotations<TModel> | null = null, TInferMode extends InferMode = DefaultInferMode> = FormModelInnerTraverse<TModel, TAnnotations, TInferMode>;
export declare type FormModel<TModel, TAnnotations extends Object | null = null> = FormModelInnerTraverse<TModel, TAnnotations>;
export declare type Replace<T extends AbstractControl> = T & {
__replace__: '__replace__';
};
Expand All @@ -15,51 +14,12 @@ export declare type FormElementArray = {
__array__: '__array__';
};
export declare type FormElementType = FormElementControl | FormElementGroup | FormElementArray;
export declare type InferModeOptional = {
__optional__: '__optional__';
};
export declare type InferModeRequired = {
__required__: '__required__';
};
export declare type InferModeFromModel = {
__frommodel__: '__frommodel__';
};
export declare type InferModeNullable = {
__nullable__: '__nullable__';
};
export declare type InferModeNonNullable = {
__nonnullable__: '__nonnullable__';
};
declare type InferMode = InferModeOptional | InferModeRequired | InferModeFromModel | InferModeNullable | InferModeNonNullable;
declare type TransformToAnnotations<T, TPrepared = RemoveOptionalFields<T>, TTraverse = T | TPrepared> = {
[key in keyof TTraverse]?: TTraverse[key] extends Array<infer U> ? (TransformToAnnotations<U> | FormElementType | [TransformToAnnotations<U>] | [FormElementType] | Replace<AbstractControl>) : TTraverse[key] extends object ? (TransformToAnnotations<TTraverse[key]> | FormElementType | [TransformToAnnotations<TTraverse[key]>] | [FormElementType] | Replace<AbstractControl>) : (FormElementType | Replace<AbstractControl>);
} | (FormElementType | Replace<AbstractControl>);
declare type RemoveOptionalFields<T> = {
declare type PrepareModel<T extends object> = {
[key in keyof T]-?: T[key];
};
declare type PrepareModel<T extends object, TInferMode extends InferMode> = TInferMode extends InferModeOptional & (InferModeNullable | InferModeNonNullable | InferModeFromModel) ? InferModeOptional & InferModeNullable extends TInferMode ? {
[key in keyof T]?: NonNullable<T[key]> | null;
} : InferModeOptional & InferModeNonNullable extends TInferMode ? {
[key in keyof T]?: NonNullable<T[key]>;
} : InferModeOptional & InferModeFromModel extends TInferMode ? {
[key in keyof T]?: T[key];
} : never : TInferMode extends InferModeRequired & (InferModeNullable | InferModeNonNullable | InferModeFromModel) ? InferModeRequired & InferModeNullable extends TInferMode ? {
[key in keyof T]-?: NonNullable<T[key]> | null;
} : InferModeRequired & InferModeNonNullable extends TInferMode ? {
[key in keyof T]-?: NonNullable<T[key]>;
} : InferModeRequired & InferModeFromModel extends TInferMode ? {
[key in keyof T]-?: T[key];
} : never : TInferMode extends InferModeFromModel & (InferModeNullable | InferModeNonNullable) ? InferModeFromModel & InferModeNullable extends TInferMode ? {
[key in keyof T]: NonNullable<T[key]> | null;
} : InferModeFromModel & InferModeNonNullable extends TInferMode ? {
[key in keyof T]: NonNullable<T[key]>;
} : never : InferModeFromModel extends TInferMode ? {
[key in keyof T]: T[key];
} : never;
declare type ApplyInferMode<T, TInferMode extends InferMode> = TInferMode extends InferModeNullable ? T | null : TInferMode extends InferModeNonNullable ? Exclude<T, null> : T;
declare type FormModelInnerKeyofTraverse<TModel extends object, TAnnotations, TInferMode extends InferMode, TPreparedModel = PrepareModel<TModel, TInferMode>> = {
[key in keyof TPreparedModel]: FormModelInnerTraverse<TModel[key], TAnnotations[key], TInferMode>;
declare type FormModelKeyofTraverse<TModel extends object, TAnnotations, TPreparedModel = PrepareModel<TModel>> = {
[key in keyof TPreparedModel]: FormModelInnerTraverse<TModel[key], TAnnotations[key]>;
};
declare type FormModelInnerTraverse<TModel, TAnnotations, TInferMode extends InferMode> = TAnnotations extends null ? TModel extends Array<any> ? FormModelInnerTraverse<TModel, FormElementArray, TInferMode> : TModel extends object ? FormModelInnerTraverse<TModel, FormElementGroup, TInferMode> : FormModelInnerTraverse<TModel, FormElementArray, TInferMode> : TAnnotations extends FormElementArray ? TModel extends Array<infer TInferredArrayValueType> ? FormArray<FormControl<ApplyInferMode<TInferredArrayValueType, TInferMode>>> : never : TAnnotations extends FormElementGroup ? TModel extends object ? FormGroup<FormModelInnerKeyofTraverse<TModel, FormElementGroup, TInferMode>> : never : TAnnotations extends FormElementControl ? FormControl<ApplyInferMode<TModel, TInferMode>> : TAnnotations extends Replace<infer TInferredReplace> ? TInferredReplace : TAnnotations extends Array<infer TInferedAnnotations> ? TModel extends Array<infer TInferedArrayType> ? FormArray<FormModelInnerTraverse<TInferedArrayType, TInferedAnnotations, TInferMode>> : never : TAnnotations extends object ? TModel extends object ? FormGroup<FormModelInnerKeyofTraverse<TModel, TAnnotations, TInferMode>> : never : FormModelInnerTraverse<TModel, FormElementControl, TInferMode>;
declare type FormModelInnerTraverse<TModel, TAnnotations> = TAnnotations extends null ? TModel extends Array<any> ? FormModelInnerTraverse<TModel, FormElementArray> : TModel extends object ? FormModelInnerTraverse<TModel, FormElementGroup> : FormModelInnerTraverse<TModel, FormElementArray> : TAnnotations extends FormElementArray ? TModel extends Array<infer TInferredArrayValueType> ? FormArray<FormControl<TInferredArrayValueType>> : never : TAnnotations extends FormElementGroup ? TModel extends object ? FormGroup<FormModelKeyofTraverse<TModel, FormElementGroup>> : never : TAnnotations extends FormElementControl ? FormControl<TModel> : TAnnotations extends Replace<infer TInferredReplace> ? TInferredReplace : TAnnotations extends Array<infer TInferedAnnotations> ? TModel extends Array<infer TInferedArrayType> ? FormArray<FormModelInnerTraverse<TInferedArrayType, TInferedAnnotations>> : never : TAnnotations extends object ? TModel extends object ? FormGroup<FormModelKeyofTraverse<TModel, TAnnotations>> : never : FormModelInnerTraverse<TModel, FormElementControl>;
export {};
//# sourceMappingURL=index.d.ts.map
2 changes: 1 addition & 1 deletion dist/src/index.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions dist/tests/infermode.test.d.ts

This file was deleted.

1 change: 0 additions & 1 deletion dist/tests/infermode.test.d.ts.map

This file was deleted.

18 changes: 8 additions & 10 deletions examples/example1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import "@angular/compiler";

import { FormBuilder, FormGroup } from "@angular/forms";
import { FormModel, InferModeNonNullable, InferModeNullable, InferModeOptional, InferModeRequired, Replace } from "../src";
import { FormModel } from "../src";

interface ModelA {
a: number;
Expand All @@ -15,24 +15,22 @@ interface ModelB {
b: number;
}

// You can pass different InferMode-s in your parts,
// for example ModelAPartForm will be Required and NonNullable,
// but ModelBPartForm will be Optional and Nullable
type ModelAPartForm = FormModel<ModelA, null, InferModeRequired & InferModeNonNullable>
type ModelBPartForm = FormModel<ModelB, null, InferModeOptional & InferModeNullable>
type ModelAPartForm = FormModel<ModelA>
type ModelBPartForm = FormModel<ModelB>

// Then you just combine controls by union operator and place it in FormGroup
type FullForm = FormGroup<ModelAPartForm['controls'] & ModelBPartForm['controls']>

const fb = new FormBuilder();
const fb = new FormBuilder().nonNullable;

// Now we can define field a without field b
const form1: FullForm = fb.group<FullForm['controls']>({
a: fb.control(42, { nonNullable: true })
b: fb.control(42),
a: fb.control(42),
})

// Or we can define both fields
const form2: FullForm = fb.group<FullForm['controls']>({
a: fb.control(42, { nonNullable: true }),
b: fb.control(42)
a: fb.control(42),
b: fb.control(42),
})
6 changes: 3 additions & 3 deletions examples/example2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Usefull when you need to omit some fields from nested models

import { FormBuilder } from "@angular/forms";
import { FormElementGroup, FormModel, InferModeNullable, InferModeRequired } from "../src";
import { FormElementGroup, FormModel } from "../src";

interface ModelA {
id: number;
Expand Down Expand Up @@ -31,9 +31,9 @@ type FormModelC = Omit<ModelC, 'modelA' | 'modelB'> & {
modelB: FormModelB;
}

type FullForm = FormModel<FormModelC, { modelA: FormElementGroup, modelB: FormElementGroup }, InferModeRequired & InferModeNullable>;
type FullForm = FormModel<FormModelC, { modelA: FormElementGroup, modelB: FormElementGroup }>;

const fb = new FormBuilder();
const fb = new FormBuilder().nonNullable;

// Now we have form without id
const form: FullForm = fb.group<FullForm['controls']>({
Expand Down
Loading

0 comments on commit 50b7838

Please sign in to comment.