Using latest form functionality

I am trying to implement a form in ionic 2 using the latest @angular/forms import. In the template I use

 <form [formGroup]="clientFormGroup" (ngSubmit)="onSubmit(clientForm.value)">
     <div formGroupName="clientFormGroupName">
        <ion-input type="text" formControlName="client_name"></ion-input>
... etc

And in the ts file:

import { REACTIVE_FORM_DIRECTIVES, FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
    templateUrl: 'build/pages/client-details/client-details.html',
    directives: [REACTIVE_FORM_DIRECTIVES],
    providers: [Database]
})
export class SomePage {
clientFormGroup = new FormGroup({
        clientFormGroupName: new FormGroup({
            client_name: new FormControl(),
            anotherControl: new FormControl(),
            etc...
        })
    })

Is it possible to add Validation to this, as used to be the case in the previous @angular/common form functionality using the FormBuilder?

FormBuilder is already there in @angular/forms

import { REACTIVE_FORM_DIRECTIVES, FormBuilder, FormGroup, Validators } from '@angular/forms';
....

form = FormGroup;
....

constructor(private fb: FormBuilder) {...}

this.form = this.fb.group({
  clientFormGroupName: ['', Validator.required]
});

no need to call new FormControl, the formBuilder does it internally

Thanks for that. Now I’m getting errors for my template: e.g for one of the FormControls …

<ion-item [class.error]="!client_name.valid && client_name.touched">
            <ion-label floating class="required-label">Given name(s)</ion-label>
            <ion-input type="text" FormControl="client_name"></ion-input>
        </ion-item>
<div *ngIf="client_name.hasError('required') && client_name.touched" class="error-box">* Name is required!</div>

The error says

TypeError: undefined is not an object (evaluating ‘self.context.client_name.hasError’)

It doesn’t seem to recognise the client_name control…

OK, I got it going as follows:

EDIT: THIS IS NOT THE SOLUTION! Refer to the 7th post in this thread for a working solution.

import { REACTIVE_FORM_DIRECTIVES, FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms';
@Component({
    templateUrl: 'build/pages/client-details/client-details.html',
    directives: [REACTIVE_FORM_DIRECTIVES],
})
export class SomePage {
    // declare the form
    clientForm: FormGroup;
    // declare all the form controls
    client_title: any;
    client_name: any;
    ...

    constructor( ... private fb: FormBuilder){
        this.clientForm = this.fb.group({
            client_title: [''],
            client_name: ['', Validators.compose([Validators.required, Validators.minLength(2)])],
            ...
        });
       // define Controls
       this.client_title = this.clientForm.controls['client_title'];
       ...
       this.client_name = this.clientForm.controls['client_name'];
    }

Any simplifying suggestions welcome …

there is nor FormControl directive ???

i think it is not necessary to create controls again on your own:

export class SomePage {
    // declare the form
    clientForm: FormGroup;

    constructor( ... private fb: FormBuilder){
        this.clientForm = this.fb.group({
            client_title: [''],
            client_name: ['', Validators.compose([Validators.required, Validators.minLength(2)])],
            ...
        });
    }
}

Template:

<form [formGroup]="clientForm">
  <input
    type="text"
    formControlName="client_title"
  >
  <input
    type="text"
    formControlName="client_name"
  >
</form>

I really appreciate you helping me out here. Your code works on its own. However the error occurs when I try to add validation. I.e.

<ion-item>
   <ion-label floating class="required-label">Given name(s)</ion-label>
   <ion-input type="text" formControlName="client_name"></ion-input>
</ion-item>

works but does no validation. While

   <ion-item [class.error]="!client_name.valid && client_name.touched">-->
      <ion-label floating class="required-label">Given name(s)</ion-label>
      <ion-input type="text" formControlName="client_name"></ion-input>
   </ion-item>
   <div *ngIf="client_name.hasError('required') && client_name.touched" class="error-box">* Name is required!</div>
   <div *ngIf="client_name.hasError('minlength') && client_name.touched" class="error-box">* Minimum name length is 2!</div>

throws the error I described above:

[Error] Error: Uncaught (in promise): EXCEPTION: Error in build/pages/client-details/client-details.html:31:13
ORIGINAL EXCEPTION: TypeError: undefined is not an object (evaluating ‘self.context.client_name.hasError’)
…
ERROR CONTEXT:
[object Object]
resolvePromise — zone.js:553
(anonymous function) — zone.js:589
invokeTask — zone.js:365
onInvokeTask — ng_zone_impl.js:44
invokeTask — zone.js:364
runTask — zone.js:265
drainMicroTaskQueue — zone.js:491
g — es6-shim.js:2194
(anonymous function) — es6-shim.js:2182
promiseReactionJob

I found the chapter “Forms in Angular 2” from the ‘ngBook’ very helpful. The following code worked.

<form [formGroup]="clientForm" (ngSubmit)="onSubmit(clientForm.value)">
   <ion-item [class.error]="!client_name.valid && client_name.touched">
      <ion-label floating class="required-label">Given name(s)</ion-label>
      <ion-input type="text" formControlName="client_name"></ion-input>
   </ion-item>
...
</form>
import { FORM_DIRECTIVES, REACTIVE_FORM_DIRECTIVES, FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms';
@Component({
    templateUrl: 'build/pages/client-details/client-details.html',
    directives: [FORM_DIRECTIVES, REACTIVE_FORM_DIRECTIVES]
})
export class ClientDetailsPage {
   clientForm: FormGroup;
   client_title: AbstractControl;
   .... // repeat for other controls

   constructor(private fb: FormBuilder, ...){
      this.clientForm = this.fb.group({
         client_name: ['', Validators.compose([Validators.required, Validators.minLength(2)])],
         ... // repeat for other controls
      });
      this.client_name = this.clientForm.controls['client_name'];
      ... // repeat for other controls
   }
}

The Validation works now …

1 Like

This thread helped but I still had some frustration getting a form going under Iconic 2 RC0. In case it might help somebody, here is what I finally got working. It is a little unusual in that it doesn’t have a submit button; instead, I am sending each change to my back end when the form field changes, using the observable and react. I went down this path because ionChange didn’t seem to be working in an ion-input, and besides, I want to use the built-in for validators. (Embarrassed to show a couple of ugly timing hacks that I hope to get rid of at some point, but what the heck.)

  <ion-item>
    <ion-label floating primary>name</ion-label>
    <ion-input formControlName="name" type="text" id="name" spellcheck="false" autocapitalize="off"></ion-input>
  </ion-item>
  <label *ngIf="!(nameCtrl.valid || nameCtrl.pristine)" class="danger" padding-left>Invalid name</label>

  <ion-item>
    <ion-label floating primary>description</ion-label>
    <ion-input formControlName="description" type="text" id="description" spellcheck="false" autocapitalize="off"></ion-input>
  </ion-item>
  <label *ngIf="!(descriptionCtrl.valid || descriptionCtrl.pristine)" class="danger" padding-left>Invalid description</label>

</form>
import {Component} from '@angular/core';
import {Services} from '../../../services/services';
import {NavController, NavParams} from 'ionic-angular';
import {DataService} from '../../../services/dataService';

import { /*Http,*/ Response/*, Headers*/ } from '@angular/http';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/debounceTime';
import { Observable } from 'rxjs/Observable';

import {FormGroup, FormBuilder, FormControl, Validators} from '@angular/forms';

@Component({
  templateUrl: 'port.html'
})
export class PortPage {

  myForm: FormGroup;
  nameCtrl: FormControl;
  descriptionCtrl: FormControl;

  id:number;
  portData:any;
  loading:boolean = true;     // to block sending initialization changes back to host
  portsCallback:any;

  constructor(fb: FormBuilder, private services: Services, private navCtrl: NavController, public navParams: NavParams, private dataService: DataService) {

    this.id = this.navParams.get('s').id;

    this.nameCtrl = fb.control('', [Validators.required, Validators.minLength(3)]);
    this.nameCtrl['name'] = 'name';
    this.descriptionCtrl = fb.control('', [Validators.required, Validators.minLength(1)]);
    this.descriptionCtrl['name'] = 'description';

    this.myForm = fb.group({
        'name': this.nameCtrl,
        'description': this.descriptionCtrl
    });
    this.nameCtrl.valueChanges
      .debounceTime(1000)
      .subscribe(newValue => this.inputChange(this.nameCtrl));
    this.descriptionCtrl.valueChanges
      .debounceTime(1000)
      .subscribe(newValue => this.inputChange(this.descriptionCtrl));
  }

  ngOnInit(refresher?:any) {
    setTimeout(() => {            // delay allows for services setup
      this.getPortDetail(this.id).subscribe(() => {
        this.nameCtrl.setValue(this.portData['name']);
        this.descriptionCtrl.setValue(this.portData['description']);
      });
    },10);
    setTimeout(() => {            // delay blocks initial form setting from triggering updatePort()
      this.loading = false;
    },2000);
  }

  private updatePort(id:number, name:string, value:any){
    this.dataService.Update('ports/' + id, '{' + name + ':' + value + '}').subscribe((res:any) => (res !== 200) && this.services.Alert('Update failed!','Pull page down to refresh'));
  };

  public inputChange(fc:FormControl) {
    if (!this.loading) {
      this.updatePort(this.id, fc['name'], fc.value);
    }
  }
}