Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle 'any' values properly in 'patchValue' #59

Merged
merged 2 commits into from
Nov 29, 2020
Merged

fix: handle 'any' values properly in 'patchValue' #59

merged 2 commits into from
Nov 29, 2020

Conversation

rafaelss95
Copy link
Contributor

@rafaelss95 rafaelss95 commented Nov 19, 2020

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our guidelines: CONTRIBUTING.md#commit
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

PR Type

What kind of change does this PR introduce?

[X] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] Other... Please describe:

What is the current behavior?

patchValue doesn't handle any properly.

Issue Number: Fixes #58

What is the new behavior?

patchValue handle any properly.

Does this PR introduce a breaking change?

[ ] Yes
[x] No

@itayod
Copy link
Contributor

itayod commented Nov 19, 2020

@rafaelss95 what about taking the implementation from here?

@rafaelss95
Copy link
Contributor Author

@rafaelss95 what about taking the implementation from here?

Hmm, let me see... doesn't it would be an overkill? I mean, Angular forms doesn't support Set, Map, only literal objects and arrays. Do you consider the proposed implementation in this PR fragile?

@itayod
Copy link
Contributor

itayod commented Nov 19, 2020

@rafaelss95 what about taking the implementation from here?

Hmm, let me see... doesn't it would be an overkill? I mean, Angular forms doesn't support Set, Map, only literal objects and arrays. Do you consider the proposed implementation in this PR fragile?

Sure, I didn't mean "copy-paste" all of it, just the relevant parts... you can skip the Set / Map part

@itayod
Copy link
Contributor

itayod commented Nov 19, 2020

Also, your fix broke some tests, please notice that

@rafaelss95
Copy link
Contributor Author

rafaelss95 commented Nov 19, 2020

Also, your fix broke some tests, please notice that

I just noticed that and I may have found another bug (not really related by these changes) :(

Look at this (e is typed as any, but it's being recognized as FormArray and thus it isn't accepted in the creation of a FormGroup as a FormControl):

Captura de Tela 2020-11-19 às 11 49 14
Captura de Tela 2020-11-19 às 11 49 29


Edit: I found the bug.

Captura de Tela 2020-11-19 às 12 02 38

The ControlsOf type

export type ControlsOf<T extends Object, TOverrides extends Partial<AbstractControlsOf<T>> = {}> = {
[key in keyof T]: unknown extends TOverrides[key] ? ControlOf<T[key]> : TOverrides[key];
};

... recognizes any values as to be FormArrays. Let me try to fix that in another PR.

@itayod
Copy link
Contributor

itayod commented Nov 19, 2020

@rafaelss95 I see that, would you like to create another PR that fixes that?

@rafaelss95
Copy link
Contributor Author

@rafaelss95 I see that, would you like to create another PR that fixes that?

Done. Checkout it out in #60.

@rafaelss95
Copy link
Contributor Author

rafaelss95 commented Nov 20, 2020

Hey @itayod could you review #60? I'm waiting its review and merge to procceed the working here.

@rafaelss95
Copy link
Contributor Author

rafaelss95 commented Nov 22, 2020

Sure, I didn't mean "copy-paste" all of it, just the relevant parts... you can skip the Set / Map part

@itayod Removing the unneeded parts of it (ReadonlySet, Set, ReadonlyMap, Map, Function), we would have this:

export type PartialDeep<T> = T extends Primitive
  ? Partial<T>
- : T extends Map<infer KeyType, infer ValueType>
- ? PartialMapDeep<KeyType, ValueType>
- : T extends Set<infer ItemType>
- ? PartialSetDeep<ItemType>
- : T extends ReadonlyMap<infer KeyType, infer ValueType>
- ? PartialReadonlyMapDeep<KeyType, ValueType>
- : T extends ReadonlySet<infer ItemType>
- ? PartialReadonlySetDeep<ItemType>
- : T extends ((...arguments: any[]) => unknown)
- ? T | undefined
  : T extends object
  ? PartialObjectDeep<T>
  : unknown;

type PartialObjectDeep<ObjectType extends object> = {
  [KeyType in keyof ObjectType]?: PartialDeep<ObjectType[KeyType]>
};

... which produces exactly the same result as the one in this PR:

export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;

@itayod
Copy link
Contributor

itayod commented Nov 24, 2020

Sure, I didn't mean "copy-paste" all of it, just the relevant parts... you can skip the Set / Map part

@itayod Removing the unneeded parts of it (ReadonlySet, Set, ReadonlyMap, Map, Function), we would have this:

export type PartialDeep<T> = T extends Primitive
  ? Partial<T>
- : T extends Map<infer KeyType, infer ValueType>
- ? PartialMapDeep<KeyType, ValueType>
- : T extends Set<infer ItemType>
- ? PartialSetDeep<ItemType>
- : T extends ReadonlyMap<infer KeyType, infer ValueType>
- ? PartialReadonlyMapDeep<KeyType, ValueType>
- : T extends ReadonlySet<infer ItemType>
- ? PartialReadonlySetDeep<ItemType>
- : T extends ((...arguments: any[]) => unknown)
- ? T | undefined
  : T extends object
  ? PartialObjectDeep<T>
  : unknown;

type PartialObjectDeep<ObjectType extends object> = {
  [KeyType in keyof ObjectType]?: PartialDeep<ObjectType[KeyType]>
};

... which produces exactly the same result as the one in this PR:

export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;

there is still T extends Primitive though I can't think of any good use case for it I think we better add that too.
Let's go for this:

 T extends object ? {
  [P in keyof T]?: DeepPartial<T[P]>;
} : Partial<T>;

@rafaelss95
Copy link
Contributor Author

rafaelss95 commented Nov 24, 2020

there is still T extends Primitive though I can't think of any good use case for it I think we better add that too.
Let's go for this:

 T extends object ? {
  [P in keyof T]?: DeepPartial<T[P]>;
} : Partial<T>;

As Partial is a utility to be used for object, the use of Partial<anything except object> in the else path would be irrelevant as it would be to handle primitives, as you said and any, for example:

Partial<number> = number or Partial<any> = any.

Besides this, this change makes the build fail:

Captura de Tela 2020-11-24 às 09 04 22

Anyway, to move forward with this PR, I've changed it accordingly. I reverted to the previous implementation.

@@ -348,7 +348,7 @@ export class FormGroup<T extends Obj = any, E extends object = any> extends NgFo
tap(value => {
if (!value) return;
handleFormArrays(this, value, arrControlFactory);
this.patchValue(value, { emitEvent: false });
this.patchValue(value as any, { emitEvent: false });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have any reference of why this is failing now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I didn't go deep but I faced this typing error:

Captura de Tela 2020-11-26 às 11 17 12

Let me know if you have a better solution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, let's try to find out why might be typescript's bug if so let's reference the issue

Copy link
Contributor Author

@rafaelss95 rafaelss95 Nov 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a Typescript bug, it happens because value in this context is just typed as Obj ({ [key: string]: any }), however patchValue expects an Observable<DeepPartial<ControlsValue>> or DeepPartial<ControlsValue> and Obj isn't assignable to ControlsValue.

You can solve this problem or using any (as you already did in (

return super.getError(errorCode as any, path) as E[K] | null;
)) or by calling super.patchValue directly.

Copy link
Contributor

@itayod itayod Nov 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your explanation doesn't make sense at all, if that's true then how come it worked before? since you didn't change patchValue signature...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I didn't change patchValue signature directly, but indirectly. Anyway, as I had some more free time today I was able to fix it by changing the conditional in DeepPartial. Let me know if we can finally move on with this. Regards.

@itayod
Copy link
Contributor

itayod commented Nov 29, 2020

@rafaelss95 LGTM

@itayod itayod merged commit 0ccdd2e into ngneat:master Nov 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Deep partial update breaks build
2 participants