1

I am creating a generic component (in Angular 5.1.0). We call this a multipart component. It allows the user to create and move parts up/down (such as employment history). I used this post as my starter and it works: Angular pass multiple templates to Component

However my component stops working when I put my component inside a form tag. I have studied it in Augery and from what I can tell, the data looks correct. However, on the initial display, all the input fields are displayed with the last item in the array! Once I correct manually in the UI, it works fine so it is an initial display problem of some sort. I put debugging bindings in to show the difference between the input fields and non-input fields.

Here is my generic multipart component:

 

    import { Component, TemplateRef, ElementRef, ContentChild, OnInit, Input, ViewChild,AfterViewInit } from '@angular/core';

    @Component({
      selector: 'vue-multipart',
      templateUrl: './vue-multipart.component.html',
      styleUrls: ['./vue-multipart.component.css']
    })
    export class VueMultipartComponent implements OnInit, AfterViewInit {

      //@Input()
      //@ViewChild('theForm') theForm;


      @Input()
      private label: string;

      @Input() itemsData: any[];
      @ContentChild(TemplateRef) itemTemplate: TemplateRef;

      constructor() {
      }

      ngOnInit() {
      }

      ngAfterViewInit() { 
            console.log(this.itemsData);
        }
      getTitleTranslation() {
        return this.label;
      }
      add() {
        this.itemsData.push({});

      }
      remove(index: number) {
        this.itemsData.splice(index, 1);
        this.markForm();
      }
      down(index: number) {
          var temp = this.itemsData[index + 1];
          this.itemsData[index + 1] = this.itemsData[index];
          this.itemsData[index] = temp;
          this.markForm();
      }
      up(index: number) {
          var temp = this.itemsData[index - 1];
          this.itemsData[index - 1] = this.itemsData[index];
          this.itemsData[index] = temp;
          this.markForm();
      }

      markForm() {
        //this.theForm.form.touched = true;
        //this.theForm.form.pristine = false;
      }
    }

Here is the template for the multipart component:

<span *ngFor="let item of itemsData; let i = index" >
<hr/>
<b>{{i + 1}} {{getTitleTranslation()}}</b>
<span *ngIf="i === 0" >
  <button (click)="down(i)">Down</button>
</span>
<span *ngIf="i > 0 && (i < (itemsData.length -1))" >
  <button (click)="down(i)">Down</button> <button (click)="up(i)">Up</button>
</span>
<span *ngIf="i > 0 && (i === (itemsData.length -1))" >
  <button (click)="up(i)">Up</button>
</span>
<button (click)="remove(i)">Remove</button><br/>
<ng-template

            ngFor let-item [ngForOf]="[item]" [ngForTemplate]="itemTemplate">

</ng-template>
</span>
<button (click)="add()">Add</button><br/>

Here is a component that uses the above multipart component:



    import { Component, OnInit, ViewChild } from '@angular/core';
    import { Hero } from '../hero';


    @Component({
      selector: 'app-component-tester',
      templateUrl: './component-tester.component.html',
      styleUrls: ['./component-tester.component.css']
    })
    export class ComponentTesterComponent implements OnInit {
      @ViewChild('myForm') myForm;


      heroes: Hero[] = [];
      multipartLabel = "Hero Detail";
      isInvalidFormSubmitted: boolean = false;

      constructor() {
        this.heroes = [
        { id: 11, name: 'Mr. Nice', power: 'Nice Guy' },
        { id: 12, name: 'Narco',power: 'Narco Guy'}, 
        { id: 13, name: 'Bombasto', power: 'Bombasto Guy' }

        ];

      }

      ngOnInit() {


      }



    }

And lastly here is that components template:

<form #myForm="ngForm"  novalidate>
  <!-- beginning of multipart test -->
  <vue-multipart [itemsData]="heroes" [label] = "multipartLabel" >

    <ng-template let-item>
      {{item.name}}, {{item.power}}
      <input #inputElement id="heroName" name="heroName" class="form-control"  [(ngModel)]="item.name" placeholder="Name" required #heroName="ngModel">
      <input #inputElement id="heroPower" name="heroPower" class="form-control"  [(ngModel)]="item.power" placeholder="Hero Power" required #heroPower="ngModel">


    </ng-template>

  </vue-multipart>
  <!-- end of multipart test -->
</form>

When the above component has the form tag, this is what I get:

enter image description here

Notice the bindings outside of the input field are correct but the input fields are always showing the last item. Inside of Augury, everything looks correct to me, just the UI input fields are not reflected. With the form tag removed, it looks great.

enter image description here

Scott
  • 485
  • 8
  • 21
  • 1
    can you replicate this in Stackblitz @Scott it will be a great starter for us to help – Rahul Singh Jan 03 '18 at 18:46
  • It's probably due to the fact that they don't have unique IDs or names, but as Rahul Singh says, it will be much easier to debug if you set up a StackBlitz or Plunker – user184994 Jan 03 '18 at 18:57
  • @RahulSingh Here you go. Nice tool I will use it from now on: https://angular-cuhjvl.stackblitz.io. Here is the editor link: https://stackblitz.com/edit/angular-cuhjvl – Scott Jan 03 '18 at 19:04

1 Answers1

0

It is because the form elements have the same name attribute.

Change it to:

[name]="item.name"

And

[name]="item.power"

And they will work.

Here is a link to a working stackblitz https://angular-vwchuh.stackblitz.io

user184994
  • 17,791
  • 1
  • 46
  • 52
  • Thanks a lot @user184994. That worked. Not sure I completely understand what was happening without it but I am relatively new to > angular 1.x – Scott Jan 03 '18 at 19:22
  • I believe that, under the hood, angular uses the name property as a key for an object, so using the same name twice, the underlying object is overwritten – user184994 Jan 03 '18 at 19:24