Reputation: 8184
Why am I getting this error?
The following code works in another application
<input class="form-control form-control-sm" type="number" min="0"
[formControl]='invoiceForm.get("quantity")'>
in this new application it works too but still complaining in the terminal
Type 'AbstractControl' is not assignable to type 'FormControl'.
Upvotes: 47
Views: 56866
Reputation: 35
Sorry about the any but it could be the solution, especially if you have different sources of keys (ex add dynamically a new control) :
public invoiceForm = this.formBuilder.group<any>({
a: new FormControl(),
b: new FormControl()
});
Upvotes: 0
Reputation: 1095
I wasn't really keen on using a workaround (which all the answers seemed to be), and happened to come across the strategy used in an Angular University example.
You can easily expose your form controls to your forms by exposing get
properties to the formControlName
on the template:
<input class="form-control form-control-sm" type="number" min="0"
formControlName='quantity'> <!-- formControlName instead of [formControl] -->
and in the component:
// The get property should match the formControlName above.
get quantity() {
return this.form.controls["quantity"];
}
Upvotes: 1
Reputation: 149
Thanks to Avetik and Imo for most of the info I am listing. The following steps work in Angular 13+:
Create a file form-control.pipe.ts in your base project folder:
import {Pipe, PipeTransform} from '@angular/core';
import {AbstractControl, FormControl} from '@angular/forms';
@Pipe({
name: 'formControl',
})
export class FormControlPipe implements PipeTransform {
transform(value: AbstractControl): FormControl {
return value as FormControl;
}
}
In the declarations section of @NgModule for your project, add FormControlPipe to the list and import it.
Anywhere you are getting this lint warning, add | FormControl (include the pipe symbol)
[formControl]="form.get('wordRuleForm').get('sound') | formControl"
Problem solved!
I had a similar control with formGroup, so I created a similar filter based on formGroup, and it is used like this:
[commonForm]="form.get('commonForm') | formGroup"
Upvotes: 6
Reputation: 8184
From this official documentation and according to the FormControl definition, we can see that FormControl inherits from AbstractControl. And since FormGroup.controls
and FormGroup.get("key")
both return an AbstractControl
, I had to create a function to cast from Parent to Child class.
toControl(absCtrl: AbstractControl): FormControl {
const ctrl = absCtrl as FormControl;
// if(!ctrl) throw;
return ctrl;
}
and the template
<input class="form-control form-control-sm" type="number" min="0"
[formControl]='toControl(invoiceForm.get("quantity"))'>
PS: I could not use FormControlName
because I have many form groups and sub groups/arrays. That's I had to use the banana in box directive, because it's value is strongly typed
EDITs 2022: Also consider @Avetik answer bellow to convert this function into a pipe for better performance
Upvotes: 11
Reputation: 391
If someone else still have this issue, I share my solution, which is almost the same as Bellash was described but with better perfomance. For this example:
<input class="form-control form-control-sm" type="number" min="0"
[formControl]='invoiceForm.get("quantity")' | formControl>
we just need to create and use pipe which will return for us value with correct type
@Pipe({
name: 'formControl',
})
export class FormControlPipe implements PipeTransform {
transform(value: AbstractControl): FormControl<typeof value['value']> {
return value as FormControl<typeof value['value']>;
}
}
In this way you will have better perfomance since pipe transform function will be called only when provided form will be changed.
Generally it is bad practice to use functions in templates, because this functions will be called on each lifecycle change, and will have big perfomance issues on bigger projects. The best way is to use pipes, which generally was developed exactly for such purposes, to transform some values on rendering only when this will be changed. Hope this will help someone)
Upvotes: 38
Reputation: 247
If there are different versions or no 'type' conversion matches you can use 'any' as a temporary solution. For e.g
From :
event: FormControl
To :
event: any
Upvotes: -3
Reputation: 47
<input type="name" placeholder="Name" class="form-control" [formControl]="$any(cardForm).controls.name" />
Here $any
function would disable type checking.
Upvotes: 1
Reputation: 199
This issue might come from tsconfig.json
if you're using Angular 9 or above
"angularCompilerOptions": {
...
"strictTemplates": true <-- causing "Type 'AbstractControl' is not assignable to type 'FormControl'"
}
First Option
by setting "strictTemplates": false
might temporary "solve" the problem
Second Option
by convert the AbstractControl
to FormControl
through template, so your .ts
file should have
convertToFormControl(absCtrl: AbstractControl | null): FormControl {
const ctrl = absCtrl as FormControl;
return ctrl;
}
and your html
<input
type="text"
[formControl]="convertToFormControl(invoiceForm.get("quantity"))"
/>
Upvotes: 15
Reputation: 447
<input type="password" placeholder="Confirm Password" class="form-control" [formControl]="$any(myForm).controls['confirmpassword']"/>
Use the $any function to disable type checking. I resolved my error by using this.
Upvotes: 43
Reputation: 8270
I was able to get around this issue by giving the template direct access to the FormControl
as well as adding it to the form in initForm()
.
.ts
form: FormGroup = new FormGroup({});
quantity: FormControl = new FormControl(Quantity.MIN, [
Validators.min(Quantity.MIN),
Validators.max(Quantity.MAX),
]);
ngOnInit(): void {
this.initForm();
}
initForm(): void {
this.form.addControl('quantity', this.quantity);
}
.html (In my case I am passing the FormControl
quantity to a custom component that takes a FormControl
Input()
named "control".)
<form [formGroup]="form" (ngSubmit)="submit()">
<app-select-quantity [control]="quantity"></app-select-quantity>
<div>
<button
mat-flat-button
type="submit"
color="primary"
[disabled]="!form.valid"
>
Submit
</button>
</div>
</form>
Upvotes: 3
Reputation: 32507
You can try
<input class="form-control form-control-sm" type="number" min="0"
[formControl]='invoiceForm.controls.quantity'>
or my personal favorite way of handlingforms
<div [formGroup]="inviceForm"> <!-- any tag that wraps all of your controls -->
<!-- some input before (anything)-->
<input class="form-control form-control-sm" type="number" min="0"
formControlName='quantity'>
<!-- some inputs after (anything)-->
</div>
Upvotes: -1