- Notifications
You must be signed in to change notification settings - Fork26.4k
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
RFC: Strictly Typed Reactive FormsAuthor:@dylhunn The goal of this RFC is to validate the design with the community, solicit feedback on open questions, and enable experimentation via a non-production-ready prototype included in this proposal. Complete: This RFC is now complete.See a summary here. MotivationConsider the following forms schema representing a party, which allows users to enter details about their very own party: typeParty={address:{house:number,street:string,},formal:boolean,foodOptions:Array<{food:string,price:number,}>} In the current version of Angular Forms, we can construct a corresponding form. Here’s such a form, populated with a default value. This default party is happening at constpartyForm=newFormGroup({address:newFormGroup({house:newFormControl(1234),street:newFormControl('Powell St')}),formal:newFormControl(false),foodOptions:newFormArray([])}); Now let's try to interact with our form. As you can see, we frequently get values of type constpartyDetails=partyForm.getRawValue();// type `any`constwhere=partyForm.get('address.street')!.value;// type `any`partyForm.controls.formal.setValue(true);// param has type `any` However, with typed forms, the types are highly specific and far more helpful: constpartyDetails=partyForm.getRawValue();// a `Party` objectconstwhere=partyForm.get('address.street')!.value;// type `string`partyForm.controls.formal.setValue(true);// param has type `boolean` These are much more useful types, and consumers that handle them incorrectly will get a compiler error (instead of a silent bug). For example, trying to do arithmetic on a value of a string control will now be an error: This illustrates the purpose of typed forms: the API now reflects the structure of the form and its data. These benefits should prove especially useful for very complex or deeply nested forms. Goals and Non-GoalsGoals
Non-Goals
This is not a redesign of Forms; improvements are narrowly focused on incrementally adding types to the existing system. Tour of the Typed APIBackwards-CompatibilityLet’s use our new API to create a constcat=newFormGroup({name:newFormControl('bob'),lives:newFormControl(9),}); Once the typed forms API is rolled out, interacting with this constname=cat.value.name;// type `string|null`cat.controls.name.setValue(42);// Error! `name` has type `string|null` Existing projects may not be 100% compatible with this stricter version of the reactive forms API at launch. To avoid build breakage, constcat=newFormGroup<any>({name:newFormControl<any>('bob'),lives:newFormControl<any>(9),}); This In practice, we will add a type alias for Nullable Controls and ResetCareful observers may note that constdog=newFormControl('spot');// dog has type FormControl<string|null>dog.reset();constwhichDog=dog.value;// null This behavior is built into the forms runtime, and so the typed forms API infers nullable controls by default. However, this can make value types more inconvenient to work with. To improve the ergonomics, we're adding the ability for constdog=newFormControl('spot',{initialValueIsDefault:true});// dog has type FormControl<string>dog.reset();constwhichDog=dog.value;// spot This gives you a choice – we’ll provide as much type safety as possible for old uses of FormGroup TypesA FormGroup infers a type based on its inner controls. Recall our constcat=newFormGroup<{name:FormControl<string>,lives:FormControl<number>,}>(...); In other words, a This may seem surprising, as one might imagine this type should describe the values instead: interfaceCat{name:string;lives:number;}constcat=newFormGroup<Cat>({name:newFormControl('spot, …),,lives:newFormControl(9,…),}); However, we want to strongly type not just Disabled ControlsThe value property of a interfaceCatValue{name?:string;lives?:number;} This may seem surprising - any given key on the value object may not be present (and thus // Disabling the `lives` key removes it from the group's value!cat.controls.lives.disable();console.log(cat.value.lives);// prints 'undefined' If you want a value object for the group that includes the values for disabled controls, use the The |
BetaWas this translation helpful?Give feedback.
All reactions
👍 195😄 16🎉 73❤️ 94🚀 57👀 13
Replies: 34 comments 80 replies
-
Great idea! It would help a lot. About this:
I hope you'll mark it as a breaking change. Because I see already how this change would mess up a lot of the code where I expect "reset" to set nulls. It might lead to very cruel bugs, especially when forms are complicated (or have multiple levels of sub-groups). I see your reasoning and I support it, but it will be a completely new behavior, so would be nice to use in the new code only, without removing the old behavior. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Yes! When we finally do this, it will be a breaking change. We'll ship it with a migration schematic to keep code that relies on the previous behavior working. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I don't know, but how about adding a new method instead of changing the reset behaviour? something like |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2👎 6
-
I really doubt that some schematic can deal with cases when reset() in one library, and code creating the form is in another. The new method is a much better idea. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
@asincole The problem is that your form wouldn't be type-safe anymore. Consider the following: constdog=newFormControl<string>('spot');dot.reset();console.log(dog.value);// null! This violates the type of `string`!
@e-oz Indeed, this would be quite tricky to migrate. We'll need to think more about the migration story here. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
This is why (3) is an open question - we want to understand if the existing behavior around resetting to
We would probably make the |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Thank you, Dylan (and the Angular team) for this RFC. 1. Is there a compelling use case for tuple-typed FormArrays? I have not seen one compelling use-case for tuple-typed FormArrays thus far. When it comes to different values/controls in FormArray, I usually reach for FormArray of FormGroup instead and the group contains more information to differentiate the value types rather than using tuple-type.
2. Is there a compelling use case for a map style FormGroup? 100%. Dynamic Forms is probably one of the reasons to use Reactive Forms in the first place. 3. Is the current nullability of FormControls useful? I personally am aware of this and always reset to default values (we do have to keep track of the default values via a class property). So to me, it is not useful at all. About this RFC which proposes to have
4. Are the limitations involving control bindings a blocking issue? I can see it as an annoyance that can be worked around with some strongly-typed pipes. But yeah, follow-up improvement with the Template Type Checker would be nice |
BetaWas this translation helpful?Give feedback.
All reactions
-
I agree, it would definitely be nicer. One of the things we consider when making these kinds of changes is the example code that exists in the wild. The problem with changing the default directly like this (even if we could make existing app code continue to work with a migration) is that example code in blogs, tutorials, etc would suddenly have different semantics, making them obsolete or at least extremely confusing. So we usually prefer to make such changes slowly, by first introducing a flag and then flipping the default as a separate step. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 8👎 1
-
This makes total sense Alex. I didn't account for existing tutorials out there. Thanks! |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
On this question:
I currently maintain a medium-sized enterprise application with some fairly involved reactive forms, and I believe handling this issue in some follow-up update in the future is perfectly fine. I would take whatever I could get on the TS side as soon as possible and worry about the template bindings side later. |
BetaWas this translation helpful?Give feedback.
All reactions
🚀 4
-
YES PLEASE!!!!!!!!!!! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2😄 3
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Hello, Thanks for the awesome RPC and feature. I wanted to ask a question about nullability of form controls. In current scenario, the returned FormControl from calling its constructor is type | null. We type our froms strongly with@ng-neat/reactive-forms. They return non-null types by default and we architected our code based around not having nulls on our models. We considered slowly migrating to official typed forms, but since new FormControl returns a nullable type, this would break A LOT OF our use cases(we use request types for transforming the model data to actual requests and those types are non-nullable mostly). And doing this in a 20-25 field model would get very tiresome really quickly: Would it be possible to set this value on the from group that the form controls reside in, and can that turn all the form controls inside the group non-nullable types? Or maybe provide another FormControl that returns non-nullable types(We can do this on our own though, no need to extend the API 🤔) I don't know just spitting ideas :) I'm just trying to find a solution to a problem that we have, I don't know the angular base at all, just wanted to share a use case where nullable form control types can be annoying and hard to migrate. Cheers! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
It's a bit confusing to allow an constnullableDog=newFormControl<string|null>=('spot');constnonNullableCat=newFormControl<string>=('tabby',{initialValueIsDefault:true});constanimals=newFormGroup({cat:nonNullableCat,dog:nullableDog,},/* hypothetical property on a group */{initialValueIsDefault:true}); What happens here? The two I agree this could be a pretty verbose to add to all your |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I agree that creating lots of functionmakeFormControl<T>(value:T):FormControl<T>{returnnewFormControl(value,{initialValueIsDefault:true});}constfg=newFormGroup({alpha:makeFormControl('a'),beta:makeFormControl('b'), ...}); |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3
-
@dylhunn Yeah I agree. Even if the confusing API is tolerated, the confusing type is problematic. Would it be possible, say, if you explicitly set the type of the FormControl to string like The schmetics could be really nice, like adding the initialValueIsDefault: true to all form controls. But this wouldn't help new code that will be written with the same verbose syntax. But if this is what is needed for typed forms, I'm happy :) |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
@alxhub Yeah I know, it is just a sweet delusion and a time bomb that is waiting to explode :) We generally try not to use reset or be careful when disabling forms, but it is a dangerous spot. That was my thought exactly, extend the FormControl class with default values or a utility function. |
BetaWas this translation helpful?Give feedback.
All reactions
-
@alxhub@fatihdogmus@dylhunn Please check here#44513 (comment) a proposal on how to implement strict nullability while giving hints to implementers. I have exactly the same feelings as you do, and I feel here we can do better. |
BetaWas this translation helpful?Give feedback.
All reactions
-
The proposal looks good, but I have a question around a specific implementation. If we give the property of the typeMyFormType={age:number;}constformGroup=newFormGroup<MyFormType>({age:newFormControl(0),});###<inputtype="number"[formControlName]="age"/> When we perform I've encountered problems in the past where I've expected the type to be a number, but because it has come from an input box on the HTML, it is still a string (as read from |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1👀 1
-
AFAIU, you can't directly pass the type of the model itself to the group. The group expects a type that consistst of AbstractControls, like this(and you need to give it nullable since typeMyFormType={age:FormControl<number|null>;}constformGroup=newFormGroup<MyFormType>({age:newFormControl(0),}); Then, you can access it direclty with <form[formGroup]="formGroup"><inputtype="number"formControlName="age"/><buttontype="button"(click)="click()">Hebele</button></form> typeMyFormType={age:FormControl<number|null>;};@Component({selector:'profile',templateUrl:'./profile.component.html',styleUrls:['./profile.component.css'],})exportclassProfileComponent{formGroup=newFormGroup<MyFormType>({age:newFormControl(0),});click(){//prints 'true'console.log(typeofthis.formGroup.get('age')?.value==='number');}} |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4
-
@fatihdogmus is correct. Just one addition: the |
BetaWas this translation helpful?Give feedback.
All reactions
-
This is great to know! That’s awesome 🎉 |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
First of all - a great thing to finally have built in typed reactive forms! I am very excited to use it.
|
BetaWas this translation helpful?Give feedback.
All reactions
😕 1
-
Indeed. Angular should provide the best practices/preferred way as default. And provideopt out or extra settings for backwards compatibility. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
I agree on the constb=newFormBuilder();constform=b.record<Pledge&{[key: `comment-${keyofPledge}`]?:string;}&{[key: `allocation-${keyofPledge}`]?:number;}>() and get a type error if I add a control to the form that is not a valid key of this object. |
BetaWas this translation helpful?Give feedback.
All reactions
-
„ I dont think the missing types in some areas are a blocker... I do think validators and CVA needs to be typed in future releases. One more thing is formControlName validating that the name actually exists in the parent form group. Didn't see it mentioned in addition to the internal type check.“ This is actually really an important thing. That’s why I use property bindings all the time when using nostack forms. Typos in control names happen so often. Actually this is the main reason for me to use no stack forms. :) |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Thank you for this awesome RFC 💪
It might be an idea to provide an extra option for the Existing forms found:- Would like to apply strong types for your existing forms? (might cause breakage): Y/n See: https://angular.io/docs?explain-how-why-etc |
BetaWas this translation helpful?Give feedback.
All reactions
-
At the very least, make the migration for the |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
The migration actually applies a special alias |
BetaWas this translation helpful?Give feedback.
All reactions
-
Well, since every property, properly has a different type. You'll have to change them one by one. So a search and replace will probably not work, right? |
BetaWas this translation helpful?Give feedback.
All reactions
-
I expect in practice you'd do a search-and-delete :) |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Your work looks great, thank you. This is what I think about this topic: (I didn't read the other comments, so please forgive me if you answered some already)
interfaceProfile{firstName:string;lastName:string;address:{street:string;city:string;};}constprofileForm=newFormGroup({firstName:newFormControl(''),lastName:newFormControl(''),address:newFormGroup({street:newFormControl(''),city:newFormControl('')})}); I expose a
// root.componentclassRoot{group=newFormGroup({})}// child.componentclassChild{constructor(root:Root){root.addControl('key',newFormControl(''))}} Moreover, dynamic forms libraries such asformly are worth looking at.
|
BetaWas this translation helpful?Give feedback.
All reactions
👍 14🎉 3
-
When I was playing with earlier versions of this PR, I felt the need to have an |
BetaWas this translation helpful?Give feedback.
All reactions
🚀 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
@NetanelBasal Thanks for your insightful comment.
We will almost certainly add a
What about
I agree this behavior is confusing and bad, but we don't have a strategy for migrating/evolving away from it. Any ideas here? (Of course, we could just tell everyone to use
You can type this three ways with the current prototype, with increasing levels of strictness:
|
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
Yes, that's correct. Custom value accessors are pretty standard. For example, you'll almost always use interfaceUser{name:string;// 👇🏻skills:FormControl<string[]>;}
Same. From my experience, you'll usually use the same control type, but let's hear what others have to say.
Now would be a good time to begin the process. Otherwise, it won't happen. You could introduce a |
BetaWas this translation helpful?Give feedback.
All reactions
👍 7
-
I totally agree disabled is currently working in a weird and unexpected way. getValue seems like a good idea! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
I almost use |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
I understand that this is probably a much heavier breaking change than you want to take but I feel like almost all of the Having an implementation of a strongly typed typeValueOf<T>=numberextendsT ?number|undefined :booleanextendsT ?boolean|undefined :T;typeTypeName<T>=Textendsstring ?'string' :Textendsnumber ?'number' :Textendsboolean ?'boolean' :never;typeTypeFromName<T>=Textends'string' ?string :Textends'number' ?number :Textends'boolean' ?boolean :never;functionsanitizeBoolean(value:unknown):boolean|undefined{if(typeofvalue==='boolean')returnvalue;if(typeofvalue==='string'){if(value==='true')returntrue;if(value==='false')returnfalse;}returnundefined;}functionsanitizeNumber(value:unknown):number|undefined{// ...}functionsanitizeString(value:unknown){// ...}classFormControlTyped<Textendsstring|number|boolean,NameextendsTypeName<T>>{constructor(private_value:ValueOf<T>,privatereadonly_type:Name){}privatetypeGuard<TUnknownextendsstring|number|boolean,UnknownNameextendsTypeName<TUnknown>>(check:UnknownName):this isFormControlTyped<TypeFromName<UnknownName>,TypeName<TypeFromName<UnknownName>>>{// without the cast, TS2367: This condition will always return 'false' since the types 'Name' and 'TypeName<TUnknown>' have no overlap.returnthis._typeasunknown===check;}getValue():ValueOf<T>{returnthis._value;}/** * sets value of form control * setting to undefined will reset to initial value * setting to empty string will set the DOM control value to empty string (potentially an invalid control state) */setValue(v:T|''|undefined){// ...if(this.typeGuard('string')){// without the cast, TS2345 : Type 'string' is not assignable to type 'ValueOf<T> & string'.this._value=sanitizeString(v)asany;}if(this.typeGuard('number')){this._value=sanitizeNumber(this._value)asany;}if(this.typeGuard('boolean')){this._value=sanitizeBoolean(this._value)asany;}thrownewError('type is invalid');}} capturing this type name for FormControl obviously is a much bigger breaking change than anything you are considering but the type considerations for |
BetaWas this translation helpful?Give feedback.
All reactions
-
The problem with Longer term, we hope to remove the defaults for the generics from this proposal, so that |
BetaWas this translation helpful?Give feedback.
All reactions
-
It looks like It feels redundant, as a FormArray returns an Array. Typing it |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
We've been thinking about this quite a bit. Pros of using
Pros of using
If we don't implement |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
I vote for |
BetaWas this translation helpful?Give feedback.
All reactions
-
The migration does not seem to pick up some components. In particular, it looks like it ignores all lazy-loaded components right now. I opened an issue with a repro, see#44524 |
BetaWas this translation helpful?Give feedback.
All reactions
-
Thank you for reporting this -- this is a major blocking issue we will need to solve. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Yep - likely we need to look for dynamic |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
To play devil's advocate, the current permissive typing enables users to opt-in to stricter typing by defining interfaces that extend the So I guess my concern is, how would these changes impact/interact with implementations like mine or |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Regarding extending Forms classes: Note that we are providing a default type Note also that extending Forms classes like this is technically unsupported and we recommend against it. We're trying to avoid breaking it though. Regarding It's becoming more and more clear that we need to type |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Regarding extending form controls - why is it an unwanted behaviour? If for example you have a reusable CVA component which encapsulates a form group with built in validation, currently you have to render it even if not shown to get the validation to work (required validation for example). You could for example extend the form control to encapsulate the validation logic and use it with the now "stateless" component |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
It's very difficult to get the types right while doing this, due to the many gotchas (
This is a good question! My suspicion would be that custom derived interfaces will either 1) be migrated or 2) benefit from the default generic parameters of classMyFormControl<T>extendsFormControl{ ...} implicitly extends Therefore, there should be no direct conflict, but your custom interfaces would need to be updated in order to benefit from any stricter typing. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
I really love this and can't wait to see it implemented. I'll play a bit with the example, but love everything you're thinking and how you plan to solve some issues now and in the future (i.e. reset(), default value vs null by default). |
BetaWas this translation helpful?Give feedback.
All reactions
-
constdog=newFormControl('spot',{initialValueIsDefault:true});// dog has type FormControl<string>dog.reset();constwhichDog=dog.value;// spot It would be nice to be able to set the default value asynchronously. Something like this: constdog=newFormControl('spot');// dog has type FormControl<string>this.getValueFrombackend.subscribe(value=>dog.setValue('beSpot',{initialValueIsDefault:true}));// ...few moments latter...dog.reset();constwhichDog=dog.value;// beSpot |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2❤️ 1🚀 1
-
What would happen if you call |
BetaWas this translation helpful?Give feedback.
All reactions
-
Let's rename it a little bit to make more sense. constdog=newFormControl('spot');// dog has type FormControl<string>dog.reset();console.log(dog.value);// null | current behaviour `Default is null`this.getValueFrombackend.subscribe(value=>{dog.setValue(value,{asDefault:true});// beSpotdog.setValue('random');dog.reset();console.log(dog.value);// beSpot});/* Maybe even like this?!this.getValueFrombackend.subscribe(value => { dog.setDefaultValue(value); // beSpot dog.setValue('random'); dog.reset(); console.log(dog.value); // beSpot})*/ I believe It should be reset to the latest default value or fall back to the current behavior. It shouldn't affect types at all. It would behave like a simple setValue but with changing the default value. |
BetaWas this translation helpful?Give feedback.
All reactions
-
In the example you gave, the type of |
BetaWas this translation helpful?Give feedback.
All reactions
-
We could definitely consider allowing the default value to be updated in the future - as long as the new default value is compatible with the type of the control, this should work fine. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Allowing to perform certain actions to controls based on the typing is a wonderful idea. However, I think we should add something more to this. Currently, controls can be added after the instantiation of a form group, that is why using I feel this the suggested approach of using non-null assertion operator hasdrawbacks:
Suggestion for an enhancement
This shouldn't be hard to implement in TS, yet would provide better ergonomics. Additionally, it will move Angular and this new feature into the recommended |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
As mentioned, this is surprising, and will affect the expected benefits of strict typing. After putting a lot of effort into creating models, type form controls and so on, you want to safely use the value of the form group and end up having a partial value. True, you can always use Proposal
|
BetaWas this translation helpful?Give feedback.
All reactions
-
While it might be surprising to some, it is how the value object currently behaves and this proposal doesn't intend to make any runtime changes to forms. Unfortunately that means that the resulting typed forms aren't strictly safe, for example you could have a |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I would love to see a readonly state added. If readonly the value would still exist in value, validators would still run, etc. The CVA interface could have a new optional setReadonlyState to set that, and if not it would set the disabled state on the CVA (assuming it at least has a setDisabledState). Most of the time in my current application when we are disabling the form we'd really rather just set inputs to readonly, but that's not easy to do with reactive forms. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
Fully agree. Readonly should exist in the same way as disabled exists. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
typeParty={address:{house:number,street:string,}, ...} What if
Will the html control be of type
Which type will prevail here?:
|
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Yes, that's right.
The type of
We currently don't have the ability to check the types against the HTML element. As discussed in theControl Bindings section above, we plan to fix this in a future version, but it would not be part of the initial release. To be more exact, in your example the type |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Shouldn't FormGroups have |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2👀 1
-
Migration to |
BetaWas this translation helpful?Give feedback.
All reactions
-
Great question. For this reason, the migration will actually insert a special symbol |
BetaWas this translation helpful?Give feedback.
All reactions
👍 7
-
Sounds like something that could be automated 😉 |
BetaWas this translation helpful?Give feedback.
All reactions
-
I already see some linters scream because of any. :-) |
BetaWas this translation helpful?Give feedback.
All reactions
-
I'm following the instructions and getting Artifact not found |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4🚀 8
-
It looks like my artifact expired off of CircleCI. I will build a fresh one, and update the instructions in about 45 minutes. |
BetaWas this translation helpful?Give feedback.
All reactions
🚀 1
-
In the stackblitz example reset code does not work:
After looking at the code, seems like default value has to be provided as a 4th argument, but that is not reflected in types. e.g.
Would work |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4🚀 5👀 4
-
Thanks, this appears to be an error in the package version on StackBlitz. It is intended to work as in your first example, and this will be fixed for the released version. |
BetaWas this translation helpful?Give feedback.
All reactions
-
The new link ishttps://1106843-24195339-gh.circle-artifacts.com/0/angular/forms-pr43834-8e5ba4f698.tgz, and I will update it above. |
BetaWas this translation helpful?Give feedback.
All reactions
🚀 2
-
It's possible to get non-existing control without getting any errors, e.g.
It seems like there are some checks for it, but this should not compile since the forms are not dynamic |
BetaWas this translation helpful?Give feedback.
All reactions
👍 5😄 1🚀 5👀 2
-
I love that approach a lot. I was using @ng-stack/forms until angular has typed forms itself. There are some other 3rd party libs that try to solve the same issue. Until now feels wrong that in an Angular project everything is typed but when you come to a more complete thing like forms you are back in the untyped magic hell. Regarding Question 3 Regarding Qestion 4: |
BetaWas this translation helpful?Give feedback.
All reactions
-
@samuelfernandez wrote "Leave reset() as it is, setting the value to null. It has always been that way, tutorials explain it." That is a good point. That's why i think the behavior should be configurable on a global level. Default should be like it was before. But lets say you want that in your project initialValueIsDefault=true is used always, a global setting would be nice and reduce time spent in code reviews. :-) |
BetaWas this translation helpful?Give feedback.
All reactions
-
Wouldn't a global setting be used by imported 3rd party modules? |
BetaWas this translation helpful?Give feedback.
All reactions
-
I am not sure if I get your point@flensrocker |
BetaWas this translation helpful?Give feedback.
All reactions
-
How do you ensure that a global setting of initialValueIsDefault only applies to your forms but not to forms in a 3rd party module? It may not work because it expects the value to be null after a reset. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Hm… valid argument. Even if it was passed in as a provider the 3rd party module would need to overwrite it. What about a config property of the component that hosts the form? Similar to change detection on push. So i can set it on any level in the component tree. And just like with on push I can opt in at component level or set it at app component and opt out if some piece of my app does not play nice with it. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Thank you for this RFC and all the work that has gone into this issue. We use This design is excellent. The biggest change I would like to see is adding strongly-typed
I haven't seen one; I can't envision a case where a FormGroup wouldn't work well enough.
Yes: I like the My preference is delineating form control containers as follows:
RE
Not as implemented - the current behavior of The disabled formcontrol behavior causing
Not blocking, and I don't see how it could be - it doesn't make things worse than they are today. But it would be great to have confidence that FormControl types and CVA types match up, if you can make that work. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I disagree. Disabled means the form field cannot be filled out. So the value is not relevant and not validated. If the field should be in the submitted form value, then the field should be set to readonly instead of disabled as it should also be part of the validation. Unfortunately reactive forms does not cover readonly FormControls yet. So it must be done on template level. As I wrote above it would be great to add this support to it. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I assume you're disagreeing with my statement about disabled formcontrols. The problems I have with the current behavior are:
I think it would be cleaner/more explicit to allow the developer to define the valid types of Alternatively, I could see value in adding |
BetaWas this translation helpful?Give feedback.
All reactions
-
Is there a chance that the typing also allows us to query which validators a control has? A common issue is that form controls are required but someone forgets to add the * in the template. Material lib for example does not know if the reactive form control is required so you always need to set the attribute manually. |
BetaWas this translation helpful?Give feedback.
All reactions
-
This is actually already possible, as of Angular 12.2. UsehasValidator: |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3
-
Oh nice. I never noticed this. Thanks! |
BetaWas this translation helpful?Give feedback.
All reactions
-
Hi, thank you for this RFC. It seems really great.
|
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
First off, very, very GLAD to see the Angular team working on strongly typed forms! Very exciting!! Can't wait for this to land. Also appreciate the RFC. Gives me the opportunity to comment on an issue I've been facing recently with Angular 12, and this concept of expanding types with So, without further ado, one of the things that worries me about Angular, is that it has the tendency toexpand types. With TypeScriptstrict mode, the use of nulladds another factor to a type. This can cause For some background, I prefer not to use TypeScript strict mode can end up causing Finally, I tend to use TypeScript in a very "Clojure-like" way, following much of the philosophy Rich Hickey takes when it comes to software development. I started learning about Clojure several years ago, and greatly enjoyed Hickey's simple, data-oriented, and highly functional approach to solving problems that often become unnecessarily complex in other languages/platforms. I've employed a lot of his philosophy in a very functional approach to writing TypeScript, which includes preferring "non-existence" to "having this special value called null" or this concept that something "may exist, or maybe not" (both concepts, as it turns out, tend to be "infectious" to code bases in one way or another...the Maybe monad is definitively infectious). That part of his philosophy is embodied in this talk he gave some years ago: https://www.youtube.com/watch?v=YR5WdGrpoug Well worth the time, for anyone who is trying to use TypeScript in a functional manner. Wonderful stuff! Specifically speaking, time index 8:00 in the above video embodies a critical factor. Hickey states it quite clearly: An "easing" of requirements should be a "compatible change", however with TypeScript strict mode, this is no longer true when adding So, with that, I read this in the first post, and it gave me great concern: constname=cat.value.name;// type `string|null`cat.controls.name.setValue(42);// Error! `name` has type `string|null` The types listed here are interfaceMyModel{name?:string;} In order for a model like this to be compatible with a form that always EXPANDS the natural type to include the, as of TypeScript Strict Mode, UNNATURAL option interfaceMyModel{name?:string|null;} Not only is this type expansion potentially unwanted, it adds complexity to code that should just be a simple optional property. Further, the use of null in the model, would then require the developer deal with the possible nulls elsewhere, potentially requiring Once again, the null isinfectious and has to grow throughout the codebase, because the underlying framework is ADDING potentiallyunwanted nullable type expansions due to the currently proposed typed forms design. As with my I would always prefer the EXPLICIT option to choose to use null if and when I choose, rather than be forced to deal with them. I very strongly believe Angular should, in all cases and at all opportunities, avoid EXPANDING types to include |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2❤️ 5
-
Same here. We try to use |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
As discussed below, we are forced into |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
After several weeks of discussion, the Typed Forms RFC is now closed. Based on the feedback and the initial prototype, we plan to move forward with the proposed design. We’ll provide more updates as we progress with the implementation and incorporate your feedback, so stay tuned! In particular, a couple action items stand out:
Thanks to all the participants for your help evolving the Angular framework! |
BetaWas this translation helpful?Give feedback.
All reactions
👍 18🎉 8❤️ 6🚀 6👀 2
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
@jrista Right. I agree with you that |
BetaWas this translation helpful?Give feedback.
All reactions
-
@dylhunn Yup, that is understood. Just wanted to lay out the benefits of using In re-reading the RFC at the top. There were some extra notes about supporting resetting to the default values. It sounds like that capability will be an option that can be enabled on day one? I am wondering, depending on the config settings the end developer uses...could that be used to dynamically adjust the return type of form value types? I've seen some incredible typing functionality with things like NgRx, where depending on configuration objects (say with the Anyway. This will be my last request, I promise. ;) |
BetaWas this translation helpful?Give feedback.
All reactions
-
@dylhunn would it be possible to remove the null type in the value when the config uses the default option? constdog=newFormControl('spot',{initialValueIsDefault:true});// dog has type FormControl<string>dog.reset();constwhichDog:string=dog.value;// spot This should be doable with TS, respects the proposed API, and gets rid of the null type in the value for teams that want strict null checks. What would be the problem of that approach? |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
Yes! This is our proposed design. |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 3
This comment was marked as disruptive content.
This comment was marked as disruptive content.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
We need a way to represent the absence of a value for a form field of type number. For example, <p-inputNumber in PrimeNG accepts null and shows a blank input, or if it has a value and the value is backspaced out, returns null. Is this going to eventually be an issue? Should we all be migrating to using undefined? (I wish TypeScript would just anoint the Option type as idiomatic). |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I would say we should avoid monads (like Option) in TypeScript myself. That is an entirely different approach to programming. You really want an entire type system that supports monadic programming if you are going to start introducing that...and if you want that, then you should probably be using Haskell which has a composable type system with monads. Regarding PrimeNG...any way you could support both null or undefined? What we now call "nullish"? It is easy enough, using if(somethingThatCouldBeNullish==null)// The "weaker" double-equals here checks if the value is equal to null, or undefined, but nothing else// Do something if somethingThatCouldBeNullish IS nullish... Using a nullish check, you could potentially refactor your codebase with a find-and-replace by looking for |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
@the Forms package currently behaves in a very unsafe way: controls reset to null, which can violate the expected value type. Yes, unsafe and it may result in verbose serialization, because null will be presented in serialization by default. Currently I have to change all properties with null to |
BetaWas this translation helpful?Give feedback.