Template error Type 'AbstractControl' is not assignable to type 'FormControl'
Asked Answered
L

10

45

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'.
Lobbyism answered 4/6, 2021 at 9:26 Comment(0)
T
41
<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.

Teakwood answered 2/8, 2021 at 11:4 Comment(1)
See @Avetik comment bellow for a much cleaner solution with a pipe. https://mcmap.net/q/182877/-template-error-type-39-abstractcontrol-39-is-not-assignable-to-type-39-formcontrol-39Gibbie
M
35

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)

Myers answered 6/6, 2022 at 12:33 Comment(6)
Exactly. I use the same exact pipe in the project. Didn't remember to update this answer. Thanks a lotLobbyism
Better solution than others. But I had to remove generics <typeof value['value']> in Angular 13.Gibbie
You mention it's bad practice to use functions in templates, but still call a function in your template: invoiceForm.get("quantity"). If the function is inexpensive like both of these are (map get and type casting), then it won't have the performance issues you mention. ...but if you truly want to get rid of the function calls, you could either add the get to your pipe, eg invoiceForm | formControl: 'quantity', or you could just use Angular's built-in formControlName instead of formControlMalda
You can't do this with TS ==> TS2315: Type 'FormControl' is not generic.Nazario
@JulienFEGER Before Angular 14, FormControl wasn't generic. Angular 14+ has replaced it with a generic type. @DanCrosby has an answer that is basically the same thing for Angular 13 and earlier.Triturate
I would recommend adding some type checking within the transform method. AbstractControl could possibly be a FormControl, FormGroup, or FormArray. If it's not actually a FormControl, casting it to one can cause hard to diagnose errors down the line. It would be better to throw an exception within transform() if you get a type different than expected, so the problem is immediately obvious.Triturate
S
13

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"))"
  />
Spell answered 26/7, 2021 at 14:25 Comment(2)
Do you mean convertToFormControl(invoiceForm.get("quantity")) ?Lobbyism
To me, this solution looks cleaner but having one drawback, template will keep calling this method continuously on each change detection cycle.Grosso
L
9

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

Lobbyism answered 4/6, 2021 at 10:45 Comment(1)
get() can also return nullAssumed
N
5

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"
Nay answered 10/7, 2022 at 20:38 Comment(1)
I would recommend adding some type checking within the transform method. AbstractControl could possibly be a FormControl, FormGroup, or FormArray. If it's not actually a FormControl, casting it to one can cause hard to diagnose errors down the line. It would be better to throw an exception within transform() if you get a type different than expected, so the problem is immediately obvious.Triturate
A
3

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>
Alphonse answered 26/7, 2021 at 1:13 Comment(1)
This feels like the cleanest solution. Building the formGroup in the controller and using [formGroup] and formControlName directive to bind things together: angular.io/guide/reactive-forms#grouping-form-controlsCastrate
P
1
<input type="name" placeholder="Name" class="form-control" [formControl]="$any(cardForm).controls.name" />

Here $any function would disable type checking.

Pralltriller answered 18/12, 2021 at 20:6 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Kingmaker
S
1

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"];
}
Sejant answered 24/6, 2023 at 4:12 Comment(1)
This is clean and perfect. ThanksForeconscious
P
-1

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>
Paloma answered 4/6, 2021 at 9:28 Comment(6)
This will result into another error controls does not exist on FormGroup. changing invoiceForm type to any will resolve the issue but I don't want to use any instead of FormGroupLobbyism
Api says it does.Paloma
invoiceForm was any but When invoiceForm is a FormGroup I still get Type 'AbstractControl' is not assignable to type 'FormControl'.Lobbyism
Use approach with the formgroup and formcontrolname and you will be finePaloma
from @angular/forms export declare class FormGroup extends AbstractControl { controls: { [key: string]: AbstractControl; };Lobbyism
Do you still have any problems? This part of API would need some improvements but apart of that, besides forcefully casting type of controls or return of get, you wont do anything about it.Paloma
P
-2

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
Photima answered 3/3, 2022 at 7:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.