Search code examples
angularangular-reactive-formsangular-ngmodelangular2-ngmodelangular-formbuilder

Understanding Angular FormBuilder with Groups and ngModel data and how/if they can be used together?


I am running into some issues with trying to use FormBuilder in my Angular app and how I can set the default values in the form based on my ngModel data.

In my class, I have the following code:

form: FormGroup;

constructor(
    private fb: FormBuilder,
) { }

ngOnInit() {

    this.form = this.fb.group({
        category: ['', [Validators.required]],
        quantity: [1, [Validators.required]],
        size: ['', [Validators.required]],
        title: ['', [Validators.required]],
    });
}

When I look at the {{ form.value | json }} in my template, it shows the original values with the empty values. So I decided to try to set the default values in the FormBuilder group like this:

    this.form = this.fb.group({
        category: [this.item.category, [Validators.required]],
        quantity: [this.item.quantity, [Validators.required]],
        size: [this.item.size, [Validators.required]],
        title: [this.item.title, [Validators.required]],
    });

But I am given these errors:

ERROR Error: formGroup expects a FormGroup instance. 
ERROR TypeError: Cannot read properties of undefined (reading 'category')
ERROR TypeError: Cannot read properties of undefined (reading 'value')

This is my template for the form:

<form [formGroup]="form">
        <ion-item lines="none">
            <ion-label position="stacked">Category</ion-label>
            <ion-select [(ngModel)]="item.category" [ngModelOptions]="{standalone: true}">
                <ion-select-option *ngFor="let category of categories" [value]="category">
                    {{ category }}
                </ion-select-option>
            </ion-select>
        </ion-item>

        <ion-item lines="none">
            <ion-label position="stacked">Title</ion-label>
            <ion-input [(ngModel)]="item.title" [ngModelOptions]="{standalone: true}"></ion-input>
        </ion-item>

        <ion-item lines="none">
            <ion-label position="stacked">Size</ion-label>
            <ion-input [(ngModel)]="item.size" [ngModelOptions]="{standalone: true}"></ion-input>
        </ion-item>

        <ion-item lines="none">
            <ion-label position="stacked">Quantity</ion-label>
            <ion-input type="number" [(ngModel)]="item.quantity" [ngModelOptions]="{standalone: true}"></ion-input>
        </ion-item>

    </form>

Any thoughts what is wrong with my approach and how I can get the default values from my this.item to appear in the form?


Solution

  • Zerospi, the problem is that you create the form before you has data in your variable "item" - this is the reason you don't see the value

    1. NEVER Mix together Reactive forms and Template-Driven (ngModel)

      The use [(ngModel)]="variable" [ngModelOptions]="{standalone: true} it's ONLY to get an input that not belong to the FormGroup.

      e.g you can has a "checkbox" to show a new input. Some like

       movil:boolean=false;
       form=new FormGroup({
         phone:new FormControl()
         mobile:new FormControl()
       })
       <form [formGroup]="form">
         Phone<input formControlName="phone">
         <input type="checkbox" [(ngModel)]="movil" 
                 [ngModelOptions]="{standalone:true}">Mobile
         <input *ngIf="movil" formControlName="mobile">
       </form>
      

      Well, really we use

         <input type="checkbox" [ngModel]="movil" 
                 (ngModelChange)="movil=$event;!$event && form.get('mobile').setValue('')
                 [ngModelOptions]="{standalone:true}">Mobile
      

      To "clean" the FormControl "mobile" if we uncheck

      How control a FormGroup?

    2. In general we can have a function in the way

      getFromGroup(data:any=null)
      {
         data=data || {phone:'',mobile:''}
         return this.fb.group({
            phone:[data.phone,Validators.required]
            mobile:data.mobile
         }
      }
      

      And use

      this.form=this.getFormGroup(this.item) //if we have an object "this.item"
      this.form=this.getFormGroup() //if we want an empty Form
      
    3. Another approach is create the form an use patchValue()

      this.form=this.fb.group({
         phone:['',Validators.required]
         mobile:''
      }
      this.form.patchValue(this.item)
      
    4. To avoid initial errors (e.g. we create the form after a call to an API), sometimes is util use some like

      <form *ngIf="form" [formGroup]="form">
      ...
      </form>
      
    5. Remember that a FormControl is disabled, don't showed in form.value, you need use form.getRawValue()