0
votes

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.

6
Your TextInputComponent in not implemented properly, there is no data binding. how it will take a input. Did you test it as a standalone componentYogendraR

6 Answers

7
votes

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

3
votes

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.

1
votes

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

1
votes

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.

0
votes

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,
    },
  ]
})
0
votes

Replace the type of control parameter from AbstractControl to Control