0

I’m working on a form that manages currencies, with fields for code and label. I’m using the latest Angular features and trying to leverage typed forms.

I have a FormArray inside a FormGroup, which is populated with some pre-filled currency data. The reactive form works as expected—I can add and remove currencies with buttons.

Currency form working fine

However, I’m facing an issue when trying to access the form control properties in my HTML template to apply validation classes (e.g., showing a valid or invalid state for the input fields). I receive the following error:

NG9: Property 'code' does not exist on type 'AbstractControl<any, any>'. [plugin angular-compiler]

src/app/countries.component.html:34:58:
  34 │ [class.is-valid]="currency.code.valid"

Problem:

Here’s the code that triggers the error:

[class.is-valid]="currency.code.valid"

I’ve also tried changing it to:

[class.is-valid]="currency.controls['code'].valid"

Or:

[class.is-valid]="currency.get('code').valid"

But I still get the same error.

My TypeScript Code:

import { CommonModule } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import {
    FormArray,
    FormBuilder,
    FormGroup,
    ReactiveFormsModule,
    Validators
} from '@angular/forms';

export interface Item {
    code: string;
    label: string;
}

@Component({
    selector: 'app-countries',
    standalone: true,
    imports: [ReactiveFormsModule, CommonModule],
    templateUrl: './countries.component.html',
    styleUrl: './countries.component.scss'
})
export class CountriesComponent {
    currencyForm: FormGroup;
    private formBuilder = inject(FormBuilder);

    constructor() {
        this.currencyForm = this.formBuilder.group({
            currencies: this.formBuilder.array<FormGroup>(
                [
                    {code: 'CAD', label: 'Canadian Dollar'},
                    {code: 'USD', label: 'United States Dollar'},
                    {code: 'EUR', label: 'Euro'}
                ].map(currency => this.createCurrencyFormGroup(currency))
            )
        });
    }

    createCurrencyFormGroup(currency: Item) {
        return this.formBuilder.group({
            code: [currency.code, [Validators.required, Validators.maxLength(3), Validators.minLength(3)]],
            label: [currency.label, Validators.required]
        });
    }

    get currencies(): FormArray {
        return this.currencyForm.get('currencies') as FormArray;
    }

    addCurrency() {
        const currencyControl = this.createCurrencyFormGroup({code: '', label: ''});
        this.currencies.push(currencyControl);
    }

    removeCurrency(index: number) {
        this.currencies.removeAt(index);
    }
}

My HTML Code:

<div class="container mt-3">
    <form [formGroup]="currencyForm" class="row g-3">
        <div class="col-md-5" formArrayName="currencies">
            <button type="button" class="btn btn-sm btn-secondary" (click)="addCurrency()">Add Currency</button>

            <div *ngFor="let currency of currencies.controls; let idx = index" [formGroupName]="idx">
                <div class="row">
                    <div class="col-4">
                        <input type="text"
                               class="form-control"
                               placeholder="Currency Code"
                               formControlName="code"
                               maxlength="3"
                               [class.is-valid]="currency.get('code').valid"
                               [class.is-invalid]="currency.get('code').invalid" />
                    </div>
                    <div class="col-6">
                        <input type="text"
                               class="form-control"
                               placeholder="Currency Label"
                               formControlName="label"
                               [class.is-valid]="currency.get('label').valid"
                               [class.is-invalid]="currency.get('label').invalid" />
                    </div>
                    <div class="col-2">
                        <button type="button" class="btn btn-danger" (click)="removeCurrency(idx)">Remove</button>
                    </div>
                </div>
            </div>
        </div>
    </form>
</div>

What I've Tried:

  • I attempted to access the control using currency.code.valid, currency.controls['code'].valid and currency.get('code').valid, but both result in the same error.
  • It seems like Angular doesn’t recognize the control properties in this way inside the template.

Question:

How can I properly access the form control properties in my FormArray to apply validation classes in the template? What am I doing wrong with my approach to accessing the code and label properties?

Thank you so much

2 Answers 2

2

TRY

Use (see that it's not only a FormArray else a FormArray of FormGroup

get currencies(): FormArray {
   return this.currencyForm.get('currencies') as FormArray<FormGroup>;
}

And use (see the '?')

[class.is-valid]="currency.get('code')?.valid"
[class.is-invalid]="currency.get('code')?.invalid"

If not work, Always can use the "dot" notation to reach the control

[class.is-valid]="currencyForm.get('currency.'+idx+'.code)?.valid"
[class.is-invalid]="currencyForm.get('currency.'+idx+'.code)?.invalid"

NOTE: You needn't use always a FormArray inside a FormGroup

You can use, e.g.

  currencies = this.formBuilder.array<FormGroup>(
    [
      { code: 'CAD', label: 'Canadian Dollar' },
      { code: 'USD', label: 'United States Dollar' },
      { code: 'EUR', label: 'Euro' },
    ].map((currency) => this.createCurrencyFormGroup(currency))
  );

And

<div *ngFor="let currency of currencies.controls; let idx = index" 
       [formGroup]="currency">
      <input ..  formControlName="code"/>
      <input ..  formControlName="label"/>
</div>
Sign up to request clarification or add additional context in comments.

Comments

1

Cannot reproduce your error with currency.get('code')!.valid in my demo, probably due to the strict type checking in my tsconfig.json was not enabled

However, you can implement the function to get the specified FormGroup from the currencies FormArray by position. This should prevent the compiler from complaining about mentioned type error.

getCurrencyFormGroupByIndex(idx: number) {
  return this.currencies.at(idx) as FormGroup;
}
<input
  type="text"
  class="form-control"
  placeholder="Currency Code"
  formControlName="code"
  maxlength="3"
  [class.is-valid]="getCurrencyFormGroupByIndex(idx).get('code')!.valid"
  [class.is-invalid]="getCurrencyFormGroupByIndex(idx).get('code')!.invalid"
/>

<input
  type="text"
  class="form-control"
  placeholder="Currency Label"
  formControlName="label"
  [class.is-valid]="getCurrencyFormGroupByIndex(idx).get('label')!.valid"
  [class.is-invalid]="getCurrencyFormGroupByIndex(idx).get('label')!.invalid"
/>

Demo @ StackBlitz

2 Comments

Another approach is working with $any() to skip type-checking: $any(currency).get("<control name>").valid.
you can always use the "dot" notation in get currencyForm.get('currency.'+idx+'.code)?.valid

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.