error TS2739: Type 'AbstractControl' is missing the following properties from type 'FormControl': registerOnChange, registerOnDisabledChange

Viewed 14331

I am a newbie in Angular and following an online course using Angular 10 but stuck at one point. I am trying to implement the Reactive forms. For that, I have added a new component text-input. The code for the text-input.component.ts is as follows:

import { Component, Input, Self } from '@angular/core';
import { ControlValueAccessor, NgControl } from '@angular/forms';

@Component({
  selector: 'app-text-input',
  templateUrl: './text-input.component.html',
  styleUrls: ['./text-input.component.css']
})
// We need to implement a control value accessor
export class TextInputComponent implements ControlValueAccessor {
  @Input() label: string;
  @Input() type = 'text';

  constructor(@Self() public ngControl: NgControl) {
    this.ngControl.valueAccessor = this;
  }
  writeValue(obj: any): void {
  }

  registerOnChange(fn: any): void {
  }

  registerOnTouched(fn: any): void {
  }
}

Here is the code for the text-input.component.html

<div class="form-group">
    <input [class.is-invalid]="ngControl.touched && ngControl.invalid" type="{{type}}" 
    class="form-control"
    [formControl]="ngControl.control" placeholder="{{label}}">

    <div *ngIf='ngControl.control.errors?.required' class="invalid-feedback">Please enter a {{label}}</div>
    <div *ngIf='ngControl.control.errors?.minlength' 
    class="invalid-feedback">{{label}} must be at least {{ngControl.control.errors.minlength['requiredLength']}}</div>

    <div *ngIf='ngControl.control.errors?.maxlength' class="invalid-feedback">{{label}} must be at most {{ngControl.control.errors.maxlength['requiredLength']}}</div>

    <div *ngIf='ngControl.control.errors?.isMatching' class="invalid-feedback">
        Password do not match</div>
</div>

But as soon as I add the code [formControl] I start getting the error:

 Type 'AbstractControl' is missing the following properties from type 'FormControl': registerOnChange, registerOnDisabledChange, _applyFormState

4     [formControl]="ngControl.control" placeholder="{{label}}">
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  src/app/_forms/text-input/text-input.component.ts:6:16
    6   templateUrl: './text-input.component.html',
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component TextInputComponent.

The course Instructor is trying to generate the input controls inside a register component using this component, the code looks like this in register.component.html

<form [formGroup]="registerForm" (ngSubmit)="register()" autocomplete="off">
    <h2 class="text-center text-primary">Sign up</h2>
    <hr>
    <app-text-input [formControl]='registerForm.controls["username"]' [label]='"Username"'></app-text-input>



    <div class="form-group text-center">
        <button class="btn btn-dark mr-2" type="submit">Register</button>
        <button class="btn btn-success mr-2" (click)="cancel()" type="button">Cancel</button>
    </div>
</form>

And there is a glimpse of register.component.ts

import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { AbstractControl, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { AccountService } from '../_services/account.service';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
  @Output() cancelRegister = new EventEmitter();
  model: any = {};
  registerForm: FormGroup;
  constructor(private accountService: AccountService, private toastr: ToastrService, private fb: FormBuilder) { }

  ngOnInit(): void {
    this.intitializeForm();
  }
  intitializeForm() {
    this.registerForm = this.fb.group({
      username: ['', Validators.required]
      
    })
  } 
  register = () => {
    console.log(this.registerForm.value);
   
  }  
}

Please do let me know what else you need, being new to Angular I am not able to find the route cause, but if you help me out then I shall move further with my course and would really thankful.

8 Answers

There are various suggestions here to cast your AbstractControl to FormControl. You can also replace the reference in the template with e.g. [formControl]="$any(ngControl.control)". The $any() cast operator is the only built in casting operator I could find for the templating engine today.

If this bothers you, please go upvote and subscribe to this extremely popular, longstanding issue that could actually provide a decent fix.

Are you using Angular 11 in strict mode here? If so change your tsconfig.json file and set:

"strictTemplates": false

and ignore the error for "Some language features are not available".

It's a problem with the "cast". You can create a getter (it's very similar when we mannage a FormArray, that we create a getter casting a FormArray)

get control(){
   return this.ngControl.control as FormControl
}

And replace in your .html all the ngControl.control by control

Using template pipe cast with generics TypeSafe:

import { Pipe, PipeTransform } from '@angular/core';

/**
 * Cast super type into type using generics
 * Return Type obtained by assignment type.
 */

@Pipe({ name: 'cast' })
export class CastPipe implements PipeTransform {
     /**
     * Cast (S: SuperType) into (T: Type) using @Generics.
     * @param value S: SuperType obtained from input type.
     * @returns (T: Type) obtained by assignment type.
     */
    transform<S, T extends S>(value: S): T {
        return <T>value;
    }
}

Usage:

[formControl]="ngControl.control | cast"

Update 1: Optional pipe argument to pass Class Type explicitly.

Update 2: doc

Instead, we need a way of capturing the type of the argument in such a way that we can also use it to denote what is being returned.

I had this problem and I noticed that there was an issue of casting form['controls'] to FormControl which is because form['controls'] is of AbstractControls type. Here is my solution implemented in your Code. text-input.component.ts

import { Component, Input, Self } from '@angular/core';
    import { ControlValueAccessor, NgControl, AbstractControl } from '@angular/forms';

    @Component({
      selector: 'app-text-input',
      templateUrl: './text-input.component.html',
      styleUrls: ['./text-input.component.css']
    })
    // We need to implement a control value accessor
    export class TextInputComponent implements OnInit {
      @Input() label: string;
      @Input() type = 'text';
      @Input() abstractcontrol!: AbstractControl;

      ngControl!: NgControl;

      constructor() {}

      ngOnInit(): void {
        this.control = this.abstractcontrol as NgControl;
      }
    }

register.component.html

<form [formGroup]="registerForm" (ngSubmit)="register()" autocomplete="off">
    <h2 class="text-center text-primary">Sign up</h2>
    <hr>
    <app-text-input [abstractcontrol]='registerForm.controls["username"]' [label]='"Username"'></app-text-input>

    <div class="form-group text-center">
        <button class="btn btn-dark mr-2" type="submit">Register</button>
        <button class="btn btn-success mr-2" (click)="cancel()" type="button">Cancel</button>
    </div>
</form>

I am also new to Angular. Please feel free to point out any problems in my solution.

In your custom reactive forms control, you may need to add providers for your component metadata.

@Component({
  selector: 'app-text-input',
  templateUrl: './text-input.component.html',
  styleUrls: ['./text-input.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => TextInputComponent),
      multi: true,
    },
  ]
})

Replace the type of control parameter from AbstractControl to Control

Are you using Angular 12 in strict mode here? If so change your tsconfig.json file and set:

"strictTemplates": false

It's works for me.

Related