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

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()