1

I have some data bindings in my app, which bind to a provider, but now I'd like to add some form validation. The problem is I get an error when I start trying to use [(ngModel)] with the form validation.

The error message I get indicates:

ngModel cannot be used to register form controls with a parent formGroup directive. Try using formGroup's partner directive \"formControlName\" instead. Example:

    <div [formGroup]=\"myGroup\">
      <input formControlName=\"firstName\">
    </div>

    In your class:

    this.myGroup = new FormGroup({
       firstName: new FormControl()
    });

      Or, if you'd like to avoid registering this form control, indicate that it's standalone in ngModelOptions:

      Example:


    <div [formGroup]=\"myGroup\">
       <input formControlName=\"firstName\">
       <input [(ngModel)]=\"showMoreControls\" [ngModelOptions]=\"{standalone: true}\">
    </div>

If I make it standalone ( [ngModelOptions]=\"{standalone: true} ) then I start getting errors ( Template parse errors: Can't bind to 'ngModelOptions' since it isn't a known property of 'ion-input'. ), but outside of that it seems that the form validation probably wouldn't work either. On the other hand if if I remove the ngModel then the data is no longer bound to my provider.

Here is a very abreviated version of my code:

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CaseProvider } from '../../providers/caseProvider';

@Component({
  selector: 'page-form',
  template: `
<ion-header>
</ion-header>

<ion-content>
    <div [formGroup]="myFormGroup">
        <ion-item>
            <ion-label>Record number</ion-label>
            <ion-input formControlName="record" [(ngModel)]="caseProvider.record"></ion-input>
        </ion-item>

        <button ion-button block (click)="saveCase()">
            <span>Save</span>
        </button>

    </div>
</ion-content>
  `
})
export class MyPage {

  myFormGroup: FormGroup;

  constructor(public formBuilder: FormBuilder, public caseProvider:CaseProvider) {
    this.myFormGroup = formBuilder.group({
        record: ['', Validators.compose([Validators.maxLength(30), Validators.pattern('.*[0-9].*')])]
    });
  }
  saveCase(){
   //save code here
  }
}

How do I get databinding to a provider (which has getters and setters in it), and do validation with a formGroup at the same time?

Thanks!

Uniphonic
  • 845
  • 12
  • 21
  • You can bind it to your formControl, and that is what you should do. So when you build form (or use patchValue) set the formControlName when you build the form... eg `firstName:[showMoreControls, Validators.required]` or whatever you have :) – AT82 Apr 13 '17 at 16:36
  • Can you show your `html` and `ts` code of your component? – Sampath Apr 13 '17 at 16:40
  • I've updated my question to show some of my code. – Uniphonic Apr 13 '17 at 18:33
  • @AJT_82 Are you saying there is a way to bind, from within a form group? It looks like your example is passing the first parameter as the variable that should be bound? I thought the first parameter passed in a form control was form state? – Uniphonic Apr 13 '17 at 18:35
  • well if you have the value in `caseProvider.record`, you can assign it like so: `record: [this.caseProvider.record, Validators.compose([Validators.maxLength(30), Validators.pattern('.*[0-9].*')])]` Take a look at this for example: http://stackoverflow.com/a/43279908/6294072 – AT82 Apr 13 '17 at 18:39
  • Okay, maybe misunderstood, seem you do not have the value, but there is no need to use ngModel, as you can access the value from the form control :) – AT82 Apr 13 '17 at 18:43
  • I can access it from the form control, but I want my provider to be updated every time it's changed. That's why I was hoping to keep bound. If I can't bind it, then that would mean I'd have to add a lot of code to each inputs on my page. Any way I can keep the databinding? – Uniphonic Apr 13 '17 at 19:00
  • @JacobH Check my updated answer, use `valueChanges` :) – AT82 Apr 13 '17 at 19:01

2 Answers2

2

You do not need to use ngModel, since you are using a reactive form, so you have your values stored in the form controls, and upon submit you can pass the complete object created by the form and use the values.

So remove ngModel, when you submit form, you have the values neatly in an object, in your case it would look like this:

{
  record: 'whatever data'
}

So when you submit, pass the form value(s) in the saveCase method:

(click)="saveCase(myFormGroup.value)"

and you will end up with all the values you have in your form.

Demo

And in your case, if you'd want to access this form control directly you can use:

console.log(this.myFormGroup.controls.record.value);

You can subscribe to the form changes using valueChanges like so:

this.myFormGroup.valueChanges.subscribe(data => {
  console.log(data); // here is whole object
})

UPDATE:

In case you have an interface (or class) for your caseProvider, you can structure the form so that it matches your model. So in this case if your interface would look like this:

export interface CaseProvider {
  record: string
}

and in your TS file you declare that variable:

caseProvider: CaseProvider;

since you have built your form so that the object you get from the form, matches your interface, you can assign the values directly in your valueChanges, since you said you need to constantly update it:

this.myFormGroup.valueChanges.subscribe(data => {
  this.caseProvider = data;
})
AT82
  • 71,416
  • 24
  • 140
  • 167
  • OK. Thanks for the response! Subscribing to the value changes sounds like the next best thing, if I can't data bind it. Seems like a fair amount of extra code though, especially if there are a lot of inputs on the page. I still wish I could just databind it, since it would be a lot cleaner and less code. :-) – Uniphonic Apr 13 '17 at 19:05
  • Well, you can build the form so, that the object you get from your form, matches an instance of your `caseProvider` right? Not really knowing enough of your setup and what *"I want my provider to be updated every time it's changed"* means here, so it's hard to help further. – AT82 Apr 13 '17 at 19:16
  • So, to summarize it sounds like your answer is saying: FormControl inputs cannot be bound to other variables, because they are already bound to the FormGroup. The closest thing you can do is subscribe to the valueChanges and then manually set the values in the Provider from there? – Uniphonic Apr 13 '17 at 19:18
  • Maybe not really understanding here, but I'll elaborate my answer shortly, and let's see if that is what you need. Is `caseProvider` an object that has an model (class or interface)? – AT82 Apr 13 '17 at 19:21
  • Good idea. I certainly could add an interface to my provider, so that the whole object could be set all at once. Thanks! – Uniphonic Apr 13 '17 at 19:42
  • Well an interface is not necessary at all (doesn't matter during runtime), only thing you would just need, is to build the form, so that it matches your object and you can directly assign it. Well, hopefully you got some ideas on how to do this! :) – AT82 Apr 13 '17 at 19:45
  • It might be a little more processor intensive though, for all variables in the provider to be simultaneously updated every time any single form element changed. If there was a way to just do it on an individual basis, such as with the ngModel tag, then that would seem to me to be the best scenario, but if that's not possible, then this might have to do. Thanks! – Uniphonic Apr 13 '17 at 19:50
0

I found more documentation on Angular forms ( https://angular.io/docs/ts/latest/guide/forms.html ) instead of Ionic (since Ionic 2 uses Angular, but Ionic was lacking some documentation here), and ended up with the following solution that allow using both ngModel and form validation:

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CaseProvider } from '../../providers/caseProvider';

@Component({
  selector: 'page-form',
  template: `
<ion-header>
</ion-header>

<ion-content>
    <form #caseForm="ngForm" (ngSubmit)="saveCase(caseForm)">
        <ion-item>
            <ion-label>Record number</ion-label>
            <ion-input #record="ngModel" [(ngModel)]="caseProvider.record" maxlength="30" pattern=".*[0-9].*"></ion-input>
        </ion-item>
        <ion-item [hidden]="record.valid || record.pristine">
            Please enter a valid record number.
        </ion-item>

        <button type="submit" ion-button block [disabled]="!caseForm.form.valid">
             <span>Save</span>
        </button>

    </form>
</ion-content>
  `
})
export class Page2 {

  constructor(public formBuilder: FormBuilder, public caseProvider:CaseProvider) {

  }
  saveCase(form){
      if(form.valid){
        //save code here
      }{
        console.log('form is not valid');
      }
  }
}
Uniphonic
  • 845
  • 12
  • 21