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

feat(forms): adding typings to form components #32450

Closed
wants to merge 4 commits into from
Closed

feat(forms): adding typings to form components #32450

wants to merge 4 commits into from

Conversation

Thomas-Waldbillig
Copy link

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.io application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

currently there is no way to have typechecking for form elements
Issue Number: N/A

What is the new behavior?

  • creating FormStatus enum (reusable)
  • adding typing to AbstractControl (value, status, valueChanges, statusChanges)
  • adding typing to FormControl (constructor, setValue, patchValue, reset)
  • adding typing to FormGroup (constructor, registerControl, addControl, removeControl, setControl, contains, setValue, patchValue, reset, getRawValue)
  • adding typing to FormArray (constructor, at, push, insert, setControl, setValue, patchValue, reset, getRawValue)

PR Checklist

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

@Thomas-Waldbillig Thomas-Waldbillig requested a review from a team as a code owner September 2, 2019 22:21
*/
public readonly value: any;
public readonly value: T|null = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't undefined also a valid value?

Copy link
Author

Choose a reason for hiding this comment

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

imho the value should match the type that was specified.
so, to reset it should use null and if the type is only composed of optional attributes you should pass an empty object.
or is there any special case where passing undefined might be important?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ultimately that doesn't matter, if you do not allow undefined here when it was allowed before, this is a breaking change.

Copy link
Author

Choose a reason for hiding this comment

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

well if you don't specify a type T defaults to any which allows undefined which should pass, no?
so the current untyped code should not be affected

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, true, any | null will just collapse to any anyway.

Copy link

Choose a reason for hiding this comment

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

The value should really be DeepPartial<T>. Even the docs say you only get the enabled controls of a FormGroup.
Because FormGroups can be nested, value can only contain a partial of each group.

@Airblader
Copy link
Contributor

I understand that emoji in commit messages are somewhat modern, but Angular doesn't use those and since the commit messages are compiled into the changelog I would consider removing it.

@trotyl
Copy link
Contributor

trotyl commented Sep 3, 2019

Partially duplicate of #20040

@Thomas-Waldbillig
Copy link
Author

Thomas-Waldbillig commented Sep 3, 2019

I understand that emoji in commit messages are somewhat modern, but Angular doesn't use those and since the commit messages are compiled into the changelog I would consider removing it.

the emoji comes from a small app that helps with commit messages (https://github.com/commitizen/cz-cli)
the emoji is linked to the type of commit and I find it quite practical when searching for a commit to locate it more easily

but I can get rid of it

@Thomas-Waldbillig
Copy link
Author

Partially duplicate of #20040

I hadn't seen that one...
b.t.w. does anyone have some recommendations/doc on how to setup and test your environment when working on the angular code base?

- creating FormStatus enum (reusable)
- adding typing to AbstractControl (value, status, valueChanges, statusChanges)
- adding typing to FormControl (constructor, setValue, patchValue, reset)
- adding typing to FormGroup (constructor, registerControl, addControl, removeControl, setControl, contains, setValue, patchValue, reset, getRawValue)
- adding typing to FormArray (constructor, at, push, insert, setControl, setValue, patchValue, reset, getRawValue)
@Thomas-Waldbillig Thomas-Waldbillig changed the title feat(forms): 🎸 adding typings to form components feat(forms): adding typings to form components Sep 3, 2019
@@ -12,19 +12,26 @@ import {composeAsyncValidators, composeValidators} from './directives/shared';
import {AsyncValidatorFn, ValidationErrors, ValidatorFn} from './directives/validators';
import {toObservable} from './validators';

export enum FormStatus {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
export enum FormStatus {
export const enum FormStatus {

@mhevery mhevery added action: merge The PR is ready for merge by the caretaker target: major This PR is targeted for the next major release labels Sep 3, 2019
@ngbot
Copy link

ngbot bot commented Sep 3, 2019

I see that you just added the PR action: merge label, but the following checks are still failing:
    failure status "ci/angular: size" is failing
    failure status "ci/circleci: build-ivy-npm-packages" is failing
    failure status "ci/circleci: build-npm-packages" is failing
    failure status "ci/circleci: legacy-unit-tests-saucelabs" is failing
    failure status "ci/circleci: lint" is failing
    failure status "ci/circleci: test" is failing
    failure status "ci/circleci: test_ivy_aot" is failing
    pending status "google3" is pending
    pending missing required status "ci/circleci: publish_snapshot"

If you want your PR to be merged, it has to pass all the CI checks.

If you can't get the PR to a green state due to flakes or broken master, please try rebasing to master and/or restarting the CI job. If that fails and you believe that the issue is not due to your change, please contact the caretaker and ask for help.

@mhevery mhevery added the area: core Issues related to the framework runtime label Sep 3, 2019
@ngbot ngbot bot added this to the needsTriage milestone Sep 3, 2019
@mhevery mhevery self-assigned this Sep 3, 2019
@mhevery
Copy link
Contributor

mhevery commented Sep 4, 2019

@Waldbillig-Thomas can you try to get this PR green?

@Thomas-Waldbillig
Copy link
Author

Thomas-Waldbillig commented Sep 4, 2019

@Waldbillig-Thomas can you try to get this PR green?

i started to look into it, but I probably won't have time to continue until this weekend

I say that there was an error when you created a formcontrol with null as value and no type, typescript infers that the type T is null and not any, which causes problems...

and for the rest, I have to check what else is failing

b.t.w. I am having some problems running scripts from the package.json locally. does anyone know where I can find some more information about this? resp. Is there an easier way to check out stuff locally?

@kara kara added action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews and removed action: merge The PR is ready for merge by the caretaker labels Sep 12, 2019
Copy link

@no0x9d no0x9d left a comment

Choose a reason for hiding this comment

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

As much as I want type checking for Angular forms but your pull request contains breaking changes.
When using typed forms Angular must support all valid use cases.
For some cases it's difficult to find the correct type. In my oppinion values should be of type DeepPartial<T>. This is not correct for FormControls but is needed for FormControls or else not all operations are possible.

*/
public readonly value: any;
public readonly value: T|null = null;
Copy link

Choose a reason for hiding this comment

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

The value should really be DeepPartial<T>. Even the docs say you only get the enabled controls of a FormGroup.
Because FormGroups can be nested, value can only contain a partial of each group.

@@ -297,7 +303,7 @@ export abstract class AbstractControl {
* the UI or programmatically.
*/
// TODO(issue/24571): remove '!'.
public readonly valueChanges !: Observable<any>;
public readonly valueChanges !: Observable<T|null>;
Copy link

Choose a reason for hiding this comment

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

Same as with value the type of valueChanges should be a DeepPartial<T> instead only type T.

@@ -1014,7 +1020,7 @@ export class FormControl extends AbstractControl {
*
*/
constructor(
formState: any = null,
formState: T|null = null,
Copy link

Choose a reason for hiding this comment

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

FormControl also accepts an object with the type {value: T, disabled: boolean}

Choose a reason for hiding this comment

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

Maybe extract to a type:

type FormState<T> = T | { value: T, disabled: boolean };

This can be used in more places as well

@@ -1072,13 +1078,13 @@ export class FormControl extends AbstractControl {
*
* @see `setValue` for options
*/
patchValue(value: any, options: {
patchValue(value: T extends Object ? Partial<T> : T, options: {
Copy link

Choose a reason for hiding this comment

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

Why can value be Partial<T>? For FormControl setValue and patchValue are the same and should have the same type. Here we can remove required properties of T. This should not be possible.

@@ -1424,7 +1430,7 @@ export class FormGroup extends AbstractControl {
* The configuration options are passed to the {@link AbstractControl#updateValueAndValidity
* updateValueAndValidity} method.
*/
patchValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}):
patchValue(value: Partial<T>, options: {onlySelf?: boolean, emitEvent?: boolean} = {}):
Copy link

Choose a reason for hiding this comment

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

value should be of type DeepPartial<T>. With only Partial<T> even the example for reactive forms in the official docs can not be typed correctly, because the method updateProfile() uses a deep partial on a FormGroup (see docs).

@@ -1837,7 +1843,7 @@ export class FormArray extends AbstractControl {
* The configuration options are passed to the {@link AbstractControl#updateValueAndValidity
* updateValueAndValidity} method.
*/
patchValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
patchValue(value: T[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
Copy link

Choose a reason for hiding this comment

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

This one is tricky. If the FormArray contains only FormControls the type of T[] is correct. But if the FormArray contains FormGroups the correct type is Partial<T>, for nested FormGroups DeepPartial<T>.
The type of T[] is not backwards compatible.

@@ -1680,8 +1686,8 @@ export class FormArray extends AbstractControl {
*
*/
constructor(
public controls: AbstractControl[],
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
public controls: AbstractControl<T[]>[],
Copy link

Choose a reason for hiding this comment

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

I think the type should be AbstractControl<T>[]

@pauldraper
Copy link
Contributor

pauldraper commented Dec 4, 2019

When using typed forms Angular must support all valid use cases.

That is a very, very odd statement for a framework that chose TypeScript.

A typed language never, ever attempts to support all legal untyped operations.

'5' - 3 is a legal JS/untyped operation, but illegal TS/typed operation. There are ways to workaround this by altering the program +'5' - 3, or by descending into untyped mode <any>'5' - 3.

Between #16828/#20040 (2 years), #31963 (4 months), and #32450 (2 months), it'd be great to see a solution for TypeScript programming that deemed acceptable.

@Airblader
Copy link
Contributor

Airblader commented Dec 4, 2019

@no0x9d didn't say that the Forms API need to support any shenanigan you can throw against it, but that it needs to support valid, existing use-cases, such as the different inputs these functions can take, dynamically adding and removing controls etc. And I definitely agree with that. Types are supposed to help us, not break APIs by not allowing us to pass valid input.

@pauldraper
Copy link
Contributor

pauldraper commented Dec 10, 2019

Types are supposed to help us, not break APIs by not allowing us to pass valid input.

And my point is that TypeScript "broke" JavaScript APIs, as do all types.

[{name: 'one'}, {name: 'two'}].sort((a, b) => (b.name < a.name) - (a.name < b.name));

That is perfectly valid JavaScript.

It is not valid TypeScript.

If you want a type-checker to prove correct uses of types, it's not unreasonable to require different programming patterns than you might be without a type checker.

Better types are of course better. But hopefully it will not take Angular another 2 years to arrive at those for something as exotic as forms.

@mr1upmachine
Copy link

@Waldbillig-Thomas do you have plans on updating this PR any further? If not, I can branch off this and begin to try and address the issues above

@Thomas-Waldbillig
Copy link
Author

Thomas-Waldbillig commented Mar 15, 2020 via email

@AndrewKushnir
Copy link
Contributor

Closing in favor of more recent #38906 and #38406. Thank you.

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jan 9, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: cleanup The PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews area: core Issues related to the framework runtime cla: yes target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet