Angular-cli: Property 'controls' does not exist on type 'AbstractControl'.

Created on 27 Apr 2017  ·  37Comments  ·  Source: angular/angular-cli

Bug Report or Feature Request (mark with an x)

- [x] bug report -> please search issues before submitting
- [ ] feature request

Versions.

@Angular/cli: 1.0.1
node: 7.5.0
os: win32 x64
Windows (10)

Repro steps.

Running the application using ng serve works fine.

using ng build works fine.

when using ng build --prod --aot or ng build --prod I get the error.

It points back to this html file and the controls property??

````html

````

The log given by the failure.

ERROR in ng:///C:/Angular/lanes4/src/app/containers/edit-user.component.html (34,13): Property 'controls' does exist on type 'AbstractControl'.

Desired functionality.

I would like to build it using --prod and --aot

Mention any other details that might be useful.

Happened since updating to version 4.0.0, i've just been using ng build

Most helpful comment

Got it!

Looked at the docs - FormArray

The FormArray class contains the controls property.

get formData() { return <FormArray>this.passwordForm.get('Data'); }

Thanks @Thisen, without you help it would've taken longer.

All 37 comments

Can be fixed by giving your component a get method:
get formData { return this.form.get('Data'); }
and then in your template:
<div class="form-group" *ngFor="let field of formData.controls; let i = index">

@Thisen, It works with ng s but I am still getting the same error when building using either ng build --prod or ng build --prod --aot.

Property 'controls ' does not exist on type 'AbstractControl'.

Thank you

Got it!

Looked at the docs - FormArray

The FormArray class contains the controls property.

get formData() { return <FormArray>this.passwordForm.get('Data'); }

Thanks @Thisen, without you help it would've taken longer.

I'm experiencing the same issue when trying to run ng build --prod with the snippet below although it seems my circumstance is a bit different. Not seeing this occur on any of my other forms?

Can either of your offer any guidance? I'm also running 4.0.0.

<form id="reset-password-form" novalidate [formGroup]="form" (ngSubmit)="submitForm()">
  <div fxLayout fxLayoutWrap>
    <!-- Current Password -->
    <div fxFlex="50%" fxFlex.xs="100%">
      <md-input-container id="currentPassword-container" class="full-width">
        <md-placeholder>Current Password</md-placeholder>
        <input id="currentPassword" mdInput required type="password" formControlName="currentPassword">
        <md-hint align="end" *ngIf="form.controls.currentPassword.dirty && form.controls.currentPassword.errors?.required">Current password is required</md-hint>
        <md-hint align="end" *ngIf="form.controls.currentPassword.dirty && form.controls.currentPassword.errors?.pattern">Must contain 8-16 characters, letters and numbers and symbols</md-hint>
      </md-input-container>
    </div>
    <div fxFlex="50%" fxFlex.xs="100%"></div>
    <div fxFlex="100%" fxLayout fxLayoutWrap formGroupName="passwords">
      <!-- New Password -->
      <div fxFlex="50%" fxFlex.xs="100%">
        <md-input-container id="password-container" class="full-width">
          <md-placeholder>New Password</md-placeholder>
          <input id="password" mdInput required type="password" formControlName="password">
          <md-hint align="end" *ngIf="form.controls.passwords.controls.password.dirty && form.controls.passwords.controls.password.errors?.required">New password is required</md-hint>
          <md-hint align="end" *ngIf="form.controls.passwords.controls.password.dirty && form.controls.passwords.controls.password.errors?.pattern">Must contain 8-16 characters, letters and numbers and symbols</md-hint>
        </md-input-container>
      </div>
      <div fxFlex="50%" fxFlex.xs="100%"></div>
      <!-- Confirm Password -->
      <div fxFlex="50%" fxFlex.xs="100%">
        <md-input-container id="confirmPassword-container" class="full-width">
          <md-placeholder>Confirm New Password</md-placeholder>
          <input id="confirmPassword" mdInput required type="password" formControlName="confirmPassword">
          <md-hint align="end" *ngIf="form.controls.passwords.controls.confirmPassword.dirty && form.controls.passwords.controls.confirmPassword.errors?.required">Password confirmation is required</md-hint>
          <md-hint align="end" *ngIf="form.controls.passwords.controls.confirmPassword.dirty && !form.controls.passwords.controls.confirmPassword.errors?.required &&form.controls.passwords.errors?.passwordMismatch">Passwords do not match</md-hint>
        </md-input-container>
      </div>
      <div fxFlex="50%" fxFlex.xs="100%"></div>
    </div>
  </div>
  <div fxLayout fxLayoutAlign="center">
    <!--Action buttons-->
    <button md-raised-button type="submit" [disabled]="!form.valid">RESET PASSWORD</button>
  </div>
</form>
ERROR in ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (22,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (22,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (22,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (23,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (23,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (23,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (32,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (32,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (32,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (33,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (33,11): Property 'controls' does not exist on type 'AbstractControl'.
ng:///usr/mystique/src/app/features/main/core-features/user-settings/reset-password/reset-password.component.html (33,11): Property 'controls' does not exist on type 'AbstractControl'.

so, my issue was solved by changing the way controls were referenced in my <md-hint> blocks

changed to form['controls'].passwords['controls'].confirmPassword.dirty.

can anyone expand one why this is necessary with forms? is it because of the change detection cycle? I find it quite curious that the error was only ocurring on one of my form when the pattern I was using was consistent all forms...

This problem still present.
If use form.get('Data').controls inside *ngFor and run ng build --prod, you will see error:
Property 'controls' does not exist on type 'AbstractControl'.

Angular CLI: 1.0.6
Angular: 4.1.3
node: 6.10.3

I can confirm this.

when using --prod the HTML fails:

Property 'controls' does not exist on type 'AbstractControl'.

For now, I'm usin workaround of passing the logic to the ts, but it's not good because it's a public method.

version: Angular 4
@angular/cli: 1.2.1
node: 8.2.1
os: darwin x64

I can run the program on localhost: 4200. When ng build -- prod --aot, I get the following error:
Property 'controls' does not exist on type 'AbstractControl'.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from "@angular/router";
import { FormGroup, FormControl, FormArray, Validators } from "@angular/forms";
import { RecipeService } from "app/recipes/recipe.service";
import { Recipe } from "app/recipes/recipe.model";

@Component({
selector: 'app-recipe-edit',
templateUrl: './recipe-edit.component.html',
styleUrls: ['./recipe-edit.component.css']
})
export class RecipeEditComponent implements OnInit {
id: number;
editMode = false;
recipeForm: FormGroup;

constructor(private route: ActivatedRoute,
private recipeService: RecipeService,
private router: Router) { }

ngOnInit() {
this.route.params.subscribe(
(params: Params) => {
this.id = +params['id'];
this.editMode = params['id'] != null;
this.initForm();
// console.log(this.editMode);
}
);
}
onSubmit() {
const newRecipe = new Recipe(
this.recipeForm.value['name'],
this.recipeForm.value['description'],
this.recipeForm.value ['imagePath'],
this.recipeForm.value ['ingredients']);

if (this.editMode) {
  this.recipeService.updateRecipe(this.id, newRecipe);
}
else {
  this.recipeService.addRecipe(newRecipe);
}

}

onClickX(index: number) {
(this.recipeForm.get('ingredients')).removeAt(index);
}

onClickCancel(){
this.router.navigate(['../'], {relativeTo: this.route})
}
onClickAddIngredient() {
(this.recipeForm.get('ingredients')).push(new FormGroup({
'name': new FormControl(null, Validators.required),
'amount': new FormControl(null,
// [Validators.required,
// Validators.pattern(/'^[1-9]+[0-9]*$'/)]
)
})
);
}
private initForm() {
let recipeName = '';
let recipeImagePath = '';
let recipeDescription = '';
let recipeIngredients = new FormArray([]);

if(this.editMode) {
const recipe = this.recipeService.getRecipe(this.id);
  recipeName = recipe.name;
  recipeImagePath = recipe.imagePath;
  recipeDescription = recipe.description;
  if(recipe['ingredients']) {
    for(let ingredient of recipe.ingredients) {
      recipeIngredients.push(
      new FormGroup({
        'name': new FormControl(ingredient.name, Validators.required),
        'amount': new FormControl(ingredient.amount, [
          Validators.required,
          // Validators.pattern(/'^[1-9]+[0-9]*$'/)
        ])
      })
    );
    }
  }
}
this.recipeForm =  new FormGroup({
  'name': new FormControl(recipeName, Validators.required),
  'imagePath': new FormControl(recipeImagePath, Validators.required),
  'description': new FormControl(recipeDescription, Validators.required),
  'ingredients': recipeIngredients
});

}
}

I am using 'get'. Can anyone help? Any suggestions?
Thank you.

La solucion mas facil es: formGroup.controls['any']['controls'].

Thank you!!

Having the same problem. ERROR in ng:///C:/Web/src/app/part-d-table/Modules/d8-tables/d8-tables.component.html (129,81): Property 'PercentVal' does not exist on type 'any[]'. ERROR in ng:///C:/Web/src/app/dashboard/dashboard.component.html (25,30): Property 'center' does not exist on type boardComponent'. ERROR in ng:Web/src/app/column-a-table/column-a-table.component.html (12,40): Property 'refresh' does not exist on type 'ColumnTableComponent'

But works when I use ng build

thnks @zpydee , this solution worked for me:
i used myForm['controls'].links['controls'] instead of myForm.controls.links.controls

What works for me (even using --prod flag) is, instead of using controls in the FormArray I use value:
*ngFor="let item of myForm.get('items').value"

formGroupName.controls['attributeName'] works for me

In my case only ng build --prod --aot was failing with Property 'controls' does not exist on type 'AbstractControl'

I changed

<div *ngFor="let arr_item of myForm.controls['some_items'].controls;let indx=index;let lst=last;" class="input-group"> 

to

<div *ngFor="let js_arr_item of getControls(myForm, 'some_items');let indx=index;let lst=last;" class="input-group">

&

getControls(frmGrp: FormGroup, key: string) {
  return (<FormArray>frmGrp.controls[key]).controls;
}

turns out, its yet another "cast" issue (pun intended) ;-)

Thanks @shammelburg souflam
Solution worked for me

change:
myForm.get('some_items').controls

to:
myForm.get('some_items')['controls'];

worked for me when using --prod

Did the same as @leandrodiniz and it also worked with --env=prod --aot.

Hi, I am using an abstract control in the form which has child component.
For some reason the child inputs get disabled. I am trying to keep it enabled all the time.

Not sure what I am doing wrong. :(

tried: this.paymentMethodForm.controls['card-number'].disable({ emitEvent: false }); --NOT working---

`import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { System } from '../system';
import { BillingAndPaymentsService } from './billing-payments.service'
import { PaymentMethod } from './payment-methods'

export abstract class AddPaymentMethodComponent implements OnChanges {
@Input() system: System;
@Input() paymentMethod: PaymentMethod;
@Input() alwaysSave = false;
@Input() isCancelable = false;
@Output() saved = new EventEmitter();
@Output() canceled = new EventEmitter();
@Input() isActive = true;

paymentMethodForm: FormGroup;
showError: boolean = false;
isLoading: boolean = false;

constructor(protected fb: FormBuilder, protected billingAndPaymentsService: BillingAndPaymentsService, controlsConfig: { [key: string]: any; }, extra?: { [key: string]: any; }) {
    controlsConfig = Object.assign(controlsConfig,
        {
            useInstallationAddress: [false],
            save: [this.alwaysSave],
            address: this.fb.group({
                street: ['', Validators.required],
                city: ['', [Validators.required, Validators.maxLength(40)]],
                state: ['', Validators.required],
                zip: ['', [Validators.required, Validators.maxLength(20)]]
            })
        });
    this.paymentMethodForm = this.fb.group(controlsConfig);

}

abstract patchPaymentMethodForm(paymentMethod: PaymentMethod): void;

toggleAddressControls() {
   //  this.paymentMethodForm.get('card-number').enable();
    if (this.paymentMethodForm.get('useInstallationAddress').value)
        this.paymentMethodForm.get('address').disable();
    else
        this.paymentMethodForm.get('address').enable();
}

onSubmit(paymentMethod: PaymentMethod) {
    if (this.paymentMethod)
        Object.assign(paymentMethod, this.paymentMethod);

    paymentMethod.isHidden = !this.paymentMethodForm.get('save').value;

    if (this.paymentMethodForm.get('useInstallationAddress').value) {
        const installationAddress = this.system.installationAddressLine2
            ? (this.system.installationAddressLine1 + ', ' + this.system.installationAddressLine2)
            : this.system.installationAddressLine1; // Create installation address line, check if line 2 is null
        paymentMethod.billingStreet = installationAddress;
        paymentMethod.billingCity = this.system.installationAddressCity;
        paymentMethod.billingState = this.system.installationAddressState;
        paymentMethod.billingPostalCode = this.system.installationAddressZipCode;
    } else {
        paymentMethod.billingStreet = this.paymentMethodForm.value['address']['street'];
        paymentMethod.billingCity = this.paymentMethodForm.value['address']['city'];
        paymentMethod.billingState = this.paymentMethodForm.value['address']['state'];
        paymentMethod.billingPostalCode = this.paymentMethodForm.value['address']['zip'];
    }

    this.isLoading = true;

    this.billingAndPaymentsService.savePaymentMethod(paymentMethod).then(result => {
        this.isLoading = false;
        return this.saved.emit(result);
    }).catch(() => {
        this.isLoading = false;
        this.showError = true;
    });

// this.paymentMethodForm.get('card-number').enable();
}

enablePaymentMethod(){

//this.paymentMethodForm.get('card-number').enable();
}

cancel() {
   //  console.log('this.paymentMethodForm.getcard-number.enable():', this.paymentMethodForm.get('card-number').enable());  --NOT Working---
   // this.paymentMethodForm.get('card-number').enable();
//console.log('this.paymentMethodForm.getcard-number.Afterenable():', this.paymentMethodForm.get('card-number').enable());
    this.canceled.emit();
}


ngOnChanges(changes: any): void {
    if (changes.paymentMethod) {
        this.paymentMethodForm.reset();
     this.paymentMethodForm.controls['card-number'].disable({ emitEvent: false });
     this.paymentMethodForm.controls['card-name'].disable({ emitEvent: false });
       this.enableCreditCardInputs();
        this.patchPaymentMethodForm(changes.paymentMethod.currentValue);
        if (changes.paymentMethod.currentValue) {
            this.paymentMethodForm.patchValue({
                useInstallationAddress: false,
                save: this.alwaysSave,

            });
            if (changes.paymentMethod.currentValue.id) {
                for (let prop in this.paymentMethodForm.controls) {
                    if (this.paymentMethodForm.controls.hasOwnProperty(prop) && this.paymentMethodForm.controls[prop] instanceof FormControl) {
                        if (prop !== 'useInstallationAddress' && prop !== 'save' && !prop.startsWith("address."))
                            this.paymentMethodForm.controls[prop].disable();
                    }
                }
            }
        } else {
            this.paymentMethodForm.patchValue({
                useInstallationAddress: false,
                save: this.alwaysSave,

            });
        }

        this.toggleAddressControls();
    }
}

}`

HTML part Child Component
@{
Layout = "_AddPaymentLayout.cshtml";
ViewBag.PaymentMethodTypeClass = "add-card-component";
}

Credit Card Number is required. Credit Card Number is invalid.
Name on Card is required.
</div>


MAIN HTML
<div class="dash-section collapse-section one-time-payment" [ngBusy]="busyOneTimePayment"> <a class="collapse-toggle ngclick" (click)="collapeseOneTimePayment()" [class.collapsed]="collapse.oneTimePayment"> @*"collapse.oneTimePayment = !collapse.oneTimePayment " ;showCancel=false;null*@ <h3 class="section-title">One Time Payment</h3> </a> <div class="collapse collapse-body" id="collapseOneTimePayment" [collapse]="collapse.oneTimePayment"> <div class="panel-group step-one" [class.hidden]= "(!isOtherOneTimePaymentSelected &&( oneTimePaymentMethod?.id || show.oneTimePayment.stepTwo || (hasCreditCardMethod && hasBankPaymentMethod)))"> <div class="panel" [class.expanded]="expand.oneTimePayment.card" [class.hidden]="expand.oneTimePayment.bank"> <div class="panel-heading" (click)="expandOneTimePayment('card')"> <h4 class="panel-title">Credit Card</h4> </div> <div class="panel-body"> <div class="panel-inner"> <add-card (saved)="createdOneTimePaymentMethod($event)" [system]="selectedSystem" [isCancelable]="true" (canceled)="cancelExpand($event, 'card')" [paymentMethod]="oneTimePaymentMethod"> </add-card> </div> </div> </div>

Why is this issue closed? Is this fixed in production builds now? Doesn't seem like it...

Running Angular 5.2.3 and was having the same problem. I have a nested FormGroup i.e.

// simplified
this.deviceForm = this.formBuilder.group({
  deviceName: '',
  advancedOptions: this.formBuilder.group({
    bridgeUrl: 'wss://api.my.site:443',
  }),
})
// this.deviceForm: FormGroup
// this.deviceForm.controls.advancedOptions: AbstractControl
// ^^ should really be type FormGroup
// this.deviceForm.controls.advancedOptions.controls.bridgeUrl
// ^^ ERROR 'controls' doesn't exist on type 'AbstractControl'

I found a few ways to get around this (thanks to y'all posting here). Assuming we have:

this.deviceForm.controls.advancedOptions.controls.bridgeUrl

We can:

1. Use .get():

this.deviceForm.get('advancedOptions')!.get('bridgeUrl')!
// Note the '!'s to tell TS to assume `get` result is non-null

2. Cast

(<FormGroup> this.deviceForm.controls.advancedOptions).controls.bridgeUrl

3. Use a custom class

// Define AbstractFormGroup
interface AbstractFormGroup extends FormGroup {
  controls: {
    [key: string]: AbstractControl & FormGroup & AbstractFormGroup,
  }
}
export class ... {
  deviceForm: AbstractFormGroup
}

// Note: This makes nested form groups (a.controls.b.controls.c... etc.)
// acceptable by the compiler, however this exposes both AbstractControl
// and FormGroup properties on all controls, so you can hit runtime errors
// if you try to use FormGroup properties when your control is just an
// AbstractControl and vice versa.

I ended up going with 1 as I think it's the cleanest

This worked for me:

form.get('passwords').get('confirmPassword').dirty

This worked for me:

form.get('passwords').get('confirmPassword').dirty

This is not allowed by angular-tslint, because functions may fire multiple times

Что работает для меня (даже используя флаг --prod), вместо использования элементов управления в FormArray я использую значение :
*ngFor="let item of myForm.get('items').value"

Спасибо!!! Всё работает!!!

In my case only ng build --prod was failing with -
Property 'controls' does not exist on type 'AbstractControl'

I changed

<div *ngFor="let item of myForm.controls['keyName'].controls;let indx=index;let lst=last;" >

to

HTML:-
<div *ngFor="let item of getControls(myForm, 'keyName');let indx=index;let lst=last;" >
TS File:-

getControls(frmGrp: FormGroup, key: string) {
  return (<FormArray>frmGrp.controls[key]).controls;
}

It worked for me!

CLI doesn't complain when ['controls'] property accessor is used.

Below worked for me

```


....


````

Please reopen the issue still occurs in Angular 5.2.0

use safe navigation operator ?
change:
myForm.get('myField').controls

to:
myForm.get('myField')?.controls

for validation errors use...

<span *ngIf="f.YOUR_FORM_KEY.controls.YOUR_FORM_KEY.errors?.YOUR_FORM_VALIDATION">YOUR_FORM_KEY is YOUR_FORM_VALIDATION</span>

eg.

<span *ngIf="f.name.controls.name.errors?.required">Name is required</span>

ts file

get f(): any {
    return this.userForm.controls;
}

This solution works for me
myForm.get('<formGroupName>').get('<formControlName>').hasError('required')

try this, it works for me
*ngFor="let item of formGroup?.get('timings')?.controls; let i = index;"

<ng-container [formGroup]="providerService.providerFG">
  <ng-container formArrayName="contentSites" *ngFor="let site of providerService.providerFG.get('contentSites').value; let i = index">
    <ng-container [formGroupName]="i">
      <input formControlName="id">
      <app-content-site [idSite]="route.snapshot.paramMap.get('id')" [index]="i" *ngIf="isSiteActivated() && route.snapshot.paramMap.get('id') === providerService.providerFG.get(['contentSites', i]).get('id').value"></app-content-site>
    </ng-container>
  </ng-container>
</ng-container>

@anonymous1983 Using *ngFor="... form.get('foo').value" instead of *ngFor="... form.get('foo').controls" does fix the template error, but it introduces reinitialization of the components within the array. This means, within your nested form you can only type one character, then it loses focus, because the nested component is getting recreated (ngOnInit is getting called for every key stroke).

My form looks like this:

form = this.fb.group({
  foo: this.fb.array([{ x: '', y: '' }]),
});

Though i'd go for *ngFor="... form.get('foo')['controls'] to prevent the template error:

<form [formGroup]="form">
  <ng-container formArrayName="foo">
    <div *ngFor="let item of form.get('foo')['controls']; let i = index">
      <app-custom-control-value-accessor [formControlName]="i"></app-custom-control-value-accessor>
    </div>
  </ng-container>
</form>

Got it!

Looked at the docs - FormArray

The FormArray class contains the controls property.

get formData() { return <FormArray>this.passwordForm.get('Data'); }

Thanks @Thisen, without you help it would've taken longer.

I'm using angular 8 at this moment and I've used
get formData() { return this.passwordForm.get('Data') as FormArray; }

La solucion mas facil es: formGroup.controls['any']['controls'].

thank you, gracias, this helped me!

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings