Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Angular 2's FormBuilder between multiple components

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

like image 735
Daniel Hair Avatar asked Aug 15 '16 07:08

Daniel Hair


2 Answers

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/

like image 55
dafyk Avatar answered Nov 07 '22 12:11

dafyk


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.

like image 43
jgenoese Avatar answered Nov 07 '22 13:11

jgenoese