Q: error TS2739: Type 'AbstractControl' is missing the following properties from type 'FormControl': registerOnChange, registerOnDisabledChange 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.

A: 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

A: 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.


A: 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.

A: 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.

A: 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".

A: 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,
    },
  ]
})


A: Even though this is an old thread but any one experiencing the same issue with Angular 13... kindly follow the following. Looks like the issue is resolved in Angular 14
text-input.component.ts
import { Component, Input, Self } from '@angular/core';
import { ControlValueAccessor, FormControl, NgControl } from '@angular/forms';

@Component({
  selector: 'app-text-input',
  templateUrl: './text-input.component.html',
  styleUrls: ['./text-input.component.css']
})
export class TextInputComponent implements ControlValueAccessor {
  
  @Input() label: string = '';
  @Input() labelMustMatch: string = '';
  @Input() placeHolder: string = '';
  @Input() type: string = 'text';

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

  writeValue(obj: any): void {
    
  }
  registerOnChange(fn: any): void {
    
  }
  registerOnTouched(fn: any): void {
    
  }
}

text-input.component.html
<input 
[class.is-invalid]="ngControl.invalid && ngControl.touched && ngControl.dirty"
type="{{ type }}"
class="form-control" 
[formControl]="$any(ngControl.control)"  
placeholder="{{ placeHolder }}"
>

<!--required-->
<div class="invalid-feedback" *ngIf="ngControl.control?.errors?.['required']">
    {{label}} is required
</div>
<!--minlength-->
<div class="invalid-feedback" *ngIf="ngControl.control?.errors?.['minlength']">
    {{label}} must be minimum {{ngControl.control?.errors?.['minlength']['requiredLength']}} characters
</div>
<!--maxlength-->
<div class="invalid-feedback" *ngIf="ngControl.control?.errors?.['maxlength']">
    {{label}} must be maximum {{ngControl.control?.errors?.['maxlength']['requiredLength']}} characters
</div>
<!--onlyChar-->
<div class="invalid-feedback" *ngIf="ngControl.control?.errors?.['onlyChar']">
    {{label}} must be only characters
</div>
<!--onlyCharWithSpace-->
<div class="invalid-feedback" *ngIf="ngControl.control?.errors?.['onlyCharWithSpace']">
    {{label}} must be only characters and space
</div>
<!--mustMatch-->
<div class="invalid-feedback" *ngIf="ngControl.control?.errors?.['mustMatch']">
    {{labelMustMatch}} do not match
</div>
<!--passwordStrength-->
<div class="invalid-feedback" *ngIf="ngControl.control?.errors?.['passwordStrength']">
    {{label}} must have an upper case, lower case and a number
</div>

site-register.component.ts
Add the getters for the three controls
  get registerFormUsernameControl(): FormControl {
    return this.registerForm.get('userName') as FormControl;
    //return this.rf['userName'] as FormControl;
  }
 
  get registerFormPasswordControl(): FormControl {
    return this.registerForm.get('password') as FormControl;
    //return this.rf['password'] as FormControl;
  }
 
  get registerFormConfirmPasswordControl(): FormControl {
    return this.registerForm.get('confirmPassword') as FormControl;
    //return this.rf['confirmPassword'] as FormControl;
  }

site-register.component.html
<app-text-input [formControl]="registerFormUsernameControl" [label]="'User name'" [placeHolder]="'user name'"></app-text-input>
<app-text-input [formControl]="registerFormPasswordControl" [type]="'password'" [label]="'Password'" [placeHolder]="'password'"></app-text-input>
<app-text-input [formControl]="registerFormConfirmPasswordControl" [type]="'password'" [label]="'Confirm password'" [placeHolder]="'confirm password'" [labelMustMatch]="'Password and confirm password'"></app-text-input>


A: 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.

A: Replace the type of control parameter from AbstractControl to Control
