7

I am trying to use FormBuilder in a page I have in Ionic 2.

First, here is my environment details: Running on Windows 10, and running ionic --version gives me 2.0.0-beta.35

Here is part of my package.json file:

...
"@angular/common": "2.0.0-rc.3",
"@angular/compiler": "2.0.0-rc.3",
"@angular/core": "2.0.0-rc.3",
"@angular/forms": "^0.3.0",
"@angular/http": "2.0.0-rc.3",
"@angular/platform-browser": "2.0.0-rc.3",
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
"ionic-angular": "2.0.0-beta.10",
"ionic-native": "1.3.2",
"ionicons": "3.0.0"
...

Second, here are the two main files involved:

insight.ts

import { Component } from '@angular/core';
import {NavController, NavParams} from 'ionic-angular';
import {
  REACTIVE_FORM_DIRECTIVES,
  FormBuilder,
  FormControl,
  FormGroup
} from '@angular/forms';
import { App, Insight } from '../../models';
import { InsightProvider } from '../../providers/insight/insight.service';
import { InsightImage, InsightLabel, InsightLink, InsightQuestion, InsightThought, InsightTodo, InsightVideo } from './shared';

@Component({
  templateUrl: 'build/pages/insight/insight.html',
  directives: [REACTIVE_FORM_DIRECTIVES, InsightImage, InsightLabel, InsightLink, InsightQuestion, InsightThought, InsightTodo, InsightVideo],
  providers: [App, InsightProvider, FormBuilder]
})
export class InsightPage {

  canAdd: boolean;
  showDetails: boolean;
  newInsight: Insight;
  insightForm: FormGroup;

  constructor(private insightProvider: InsightProvider,
              private params: NavParams) {
    this.insightForm = new FormGroup({
      type: new FormControl('', []),
      todo: new FormControl('', []),
      checked: new FormControl(false, []),
      imageUrl: new FormControl('', []),
      link: new FormControl('', []),
      url: new FormControl('', []),
      label: new FormControl('', []),
      question: new FormControl('', []),
      answer: new FormControl('', []),
      title: new FormControl('', []),
      details: new FormControl('', []),
    });
  }

  ngOnInit() {
    this.canAdd = false;
    this.showDetails = true;
  }

  addNewInsight() {
    if (this.newInsight.type) {
      this.insightProvider.createInsight(this.newInsight)
      .subscribe(response => {
        this.newInsight.setId(response.data.id);
        this.newInsight.title = '';
        console.log(response);
      });
    }
  }

  deleteClicked(index: number) {
    console.log('Clicked on ' + index);
    this.insightProvider.deleteInsight(this.newInsight)
    .subscribe(data => {
      console.log(data);
    });
  }


}

insight.html

<form [ngFormModel]="insightForm" (ngSubmit)="createNewInsight()">
      <ion-item>
        <ion-label for="type">Insight Type</ion-label>
        <ion-select name="type" id="type" [formControl]="type">
          <ion-option value="label">Label</ion-option>
          <ion-option value="thought">Thought</ion-option>
          <ion-option value="link">Link</ion-option>
          <ion-option value="question">Question</ion-option>
          <ion-option value="todo">Todo</ion-option>
          <ion-option value="image">Image</ion-option>
          <ion-option value="video">Video</ion-option>
        </ion-select>
      </ion-item>

      <div [ngSwitch]="type">
          <insight-image [form]="insightForm" *ngSwitchCase="'image'"></insight-image>
          <insight-label [form]="insightForm" *ngSwitchCase="'label'"></insight-label>
          <insight-link [form]="insightForm" *ngSwitchCase="'link'"></insight-link>
          <insight-question [form]="insightForm" *ngSwitchCase="'question'"></insight-question>
          <insight-thought [form]="insightForm" *ngSwitchCase="'thought'"></insight-thought>
          <insight-todo [form]="insightForm" *ngSwitchCase="'todo'"></insight-todo>
          <insight-video [form]="insightForm" *ngSwitchCase="'video'"></insight-video>
      </div>

      <button type="submit" block primary text-center (click)="addNewInsight()" [disabled]="!newInsight.type">
        <ion-icon name="add"></ion-icon> Add Insight
      </button>
    </form>

As you can see, I am trying to pass the FormGroup Object into multiple components so that I could use them.

Here is an example of what one of the components look like (minimal version right now):

<ion-item>
  <ion-label floating for="link">Link</ion-label>
  <ion-input type="text" name="link" id="link" [formControl]="link"></ion-input>
</ion-item>

<ion-item>
  <ion-label floating for="url">URL</ion-label>
  <ion-input type="text" id="url" name="url" [formControl]="url"></ion-input>
</ion-item>

The problem I am facing right now is this error:

Error from FormBuilder and Ionic 2

What I believe is happening is that the FormBuilder is looking for the given names I declare in my typescript file (such as todo, imageUrl, link, etc), but since it is in my other components, it errors out, thinking its not there.

What could be the reason for this error? I have looked online and could not find related issues.

FYI, the reason I am needing them in components and not in the same page, is because in the future, the functionality will be different for each input, thus needed to give each component a "Single Responsibility".

Thanks in advance

Daniel Hair
  • 240
  • 1
  • 2
  • 14
  • You did not mention version of Ionic2 you are using, it may be important. I am not sure but the error says "unspecified name" so try using `ngControl="url"` instead of `[formControl]="url"` – dafyk Aug 15 '16 at 08:26
  • I just updated my question to include my environment details at the top. Thank you for reminding me to let you know about my environment. I tried ngControl="url", but I am now getting "No provider for ControlContainer". I added "ControlContainer" as a provider in my component, but receiving the same error. Just in case, I tried doing formControl="url" I get the same error – Daniel Hair Aug 15 '16 at 22:43
  • 1
    Well Ionic2 is still beta, expect some breaking changes according to [Forms Upcoming Change Proposal](https://docs.google.com/document/u/1/d/1RIezQqE4aEhBRmArIAS1mRIZtWFf6JxN_7B4meyWK0Y/pub). My next guess is to use `formControlName="url"` – dafyk Aug 15 '16 at 23:52
  • Thanks for that. I tried using `formControlName="url"`, but now I'm getting `No provider for NgControl`. I had `FORM_DIRECTIVES` in my component directives declaration, but to no avail. I also tried adding NgControl to my providers. Same result. – Daniel Hair Aug 22 '16 at 19:19

3 Answers3

20

For every one else with problem

Cannot find control with unspecified name attribute.

The problem is you forgot to define formControlName on your form input elements.

formControlName="url"

If you are facing No provider for NgControl after fixing inputs you have a problem with Ionic2 breaking changes in Fom handling. There is a chance you can fix your code by importing a new form component:

import { disableDeprecatedForms, provideForms } from '@angular/forms';

But you will probably still face more and more problems. To really fix your code:

  • update to the latest beta version
  • simplify your form to 2 simple inputs
  • rewrite your form and make it work
  • add rest of your elements

Good tutorial about FormBuilder and validation https://blog.khophi.co/ionic-2-forms-formbuilder-and-validation/

Sergey
  • 1,384
  • 1
  • 24
  • 34
dafyk
  • 1,022
  • 12
  • 23
  • 4
    Thanks! I was using `[formControlName]="url"` instead of `formControlName="url"` – Kevin Doyon Dec 02 '16 at 17:23
  • If you are using formArrays you need to specify the name in `formArrayName="name"` in a div surrounding your *ngFor loop that prints the multiple forms – triadiktyo May 05 '17 at 16:50
3

As a general note in Angular 2+, I found that adopting a consistent approach to specifying formGroupName, formArrayName, and formGroupNamesaved a great deal of time, and what remained of my sanity. I lost much time before I did.

Either use

formXXXName="literal value"

Or

formXXXName="{{value expression}}"

Or

[formXXXName]="'literal value'" (single tick within double tick)

Or

[formXXXName]="value expression"

I was continually mixing them, to my great sorrow.

Example from a recent project:

Case 1: Correct use of bracket notation.

    <label>Notes</label>
    <div class="panel panel-default">
        <div class="panel-body">
            <div [formArrayName]="'notes'">
                <div *ngFor="let note of outletForm.controls.notes.controls; let i=index">
                    <input type="text" [formControlName]="i" class="form-control" />
                </div>
            </div>
        </div>
    </div>

Case 2: Correct use of non-bracket notation.

    <label>Notes</label>
    <div class="panel panel-default">
        <div class="panel-body">
            <div formArrayName="notes">
                <div *ngFor="let note of outletForm.controls.notes.controls; let i=index">
                    <input type="text" formControlName="{{i}}" class="form-control" />
                </div>
            </div>
        </div>
    </div>

Please note in the above the use of "{{" and "}}" to compel Angular to treat what would normally be a literal string value (because of the use of the no-brackets syntax) as an EL expression.

Case 3: Incorrect use of bracket notation

    <label>Notes</label>
    <div class="panel panel-default">
        <div class="panel-body">
            <div [formArrayName]="notes">
                <div *ngFor="let note of outletForm.controls.notes.controls; let i=index">
                    <input type="text" formControlName="{{i}}" class="form-control" />
                </div>
            </div>
        </div>
    </div>

In this case, the value of formArrayName, "notes", is treated as an EL expression. Since my component has no such member, it evaluates to nothing. Therefore, the following error is sustained:

OutletFormComponent.html:153 ERROR Error: Cannot find control with unspecified name attribute
    at _throwError (forms.es5.js:1830)
    at setUpFormContainer (forms.es5.js:1803)
    at FormGroupDirective.addFormArray (forms.es5.js:4751)
    at FormArrayName.ngOnInit (forms.es5.js:5036)
    at checkAndUpdateDirectiveInline (core.es5.js:10793)
    at checkAndUpdateNodeInline (core.es5.js:12216)
    at checkAndUpdateNode (core.es5.js:12155)
    at debugCheckAndUpdateNode (core.es5.js:12858)
    at debugCheckDirectivesFn (core.es5.js:12799)
    at Object.eval [as updateDirectives] (OutletFormComponent.html:159)

Fortunately, the line number given at the very bottom of the stack points to the line with the problem.

Case 4: Incorrect use of non-bracketing notation

    <label>Notes</label>
    <div class="panel panel-default">
        <div class="panel-body">
            <div [formArrayName]="'notes'">
                <div *ngFor="let note of outletForm.controls.notes.controls; let i=index">
                    <input type="text" formControlName="i" class="form-control" />
                </div>
            </div>
        </div>
    </div>

In this case, the value of formControlName, "i", will be interpreted as a literal string, but we really intended to refer to the index variable 'i' on the containing *ngFor directive. The result of this error is:

OutletFormComponent.html:161 ERROR Error: Cannot find control with path: 'notes -> i'
    at _throwError (forms.es5.js:1830)
    at setUpControl (forms.es5.js:1738)
    at FormGroupDirective.addControl (forms.es5.js:4711)
    at FormControlName._setUpControl (forms.es5.js:5299)
    at FormControlName.ngOnChanges (forms.es5.js:5217)
    at checkAndUpdateDirectiveInline (core.es5.js:10790)
    at checkAndUpdateNodeInline (core.es5.js:12216)
    at checkAndUpdateNode (core.es5.js:12155)
    at debugCheckAndUpdateNode (core.es5.js:12858)
    at debugCheckDirectivesFn (core.es5.js:12799)

Thanks and kudos to all Angular2+ developers and experts.

jgenoese
  • 31
  • 3
0

When using the FormBuilder, in your template you have to define the control so that the FormBuilder can identify the control.

So in your template which has a formGroup named 'insightForm' with a control 'url':

<form [formGroup]="insightForm" (ngSubmit)="onSubmit(insightForm.value)">
    <ion-input [formControl]="insightForm.controls['url']"></ion-input>
</form>
Kavo
  • 443
  • 1
  • 5
  • 12