2
votes

I'm creating an app that allows users to add categories and reorder them. When pressing "add category" it creates a new row in the table and an input form displays to allow the user to name that category. However, I can not send the form data to the 'save' button if that input has *ngIf within it. I even tried putting the if statement on a div surrounding the input to no avail. I'm just getting that the input is undefined.

categories.component.html

<div class="container table-responsive">
  <table class="table table-striped table-hover">
    <thead>
      <p class="ml-3 mt-2 mb-0 float-left">Section</p>
      <a href="#" class="add-category mt-3 mr-3" (click)="addCatagory()">+ ADD CATEGORY</a>
      <tr>
        <th scope="col"></th>
        <th scope="col">CATEGORY</th>
        <th scope="col" class="mt-3">SEQ.</th>
        <th scope="col"></th>
      </tr>
    </thead>
    <tbody dragula="categories" [(dragulaModel)]="sortableList" id="tbody">
      <tr *ngFor="let row of sortableList; index as i;">
        <th scope="row" class="sort-cell">
          <button class="btn bg-transparent">
            <img src="/assets/images/sortable.svg" alt="">
          </button>
        </th>
        <td>
          <p *ngIf="!row.editing">
            {{row.title}}
          </p>

          <input #box placeholder="Category Name">

        </td>
        <td>
          <p class="seq text-white text-center">{{i + 1}}</p>
        </td>
        <td class="text-right">
          <button class="btn bg-transparent border-0" (click)="delCatagory(row.title)">
            <img *ngIf="!row.editing" src="/assets/images/delete.svg" alt="">
          </button>
          <a *ngIf="row.editing" href="#" class="add-category mt-3 mr-3" (click)="senddata(box.value, i)">Save</a>
        </td>
      </tr>
    </tbody>
  </table>
</div>

categories.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-categories',
  templateUrl: './categories.component.html',
  styleUrls: ['./categories.component.scss']
})
export class CategoriesComponent implements OnInit {
  sortableList = [];

  senddata(value, i): void {
    var a = value;
    var current = this.sortableList.length;
    console.log('Value ' + a);
    console.log('Current ' + current);
    this.sortableList.splice(i, 1, {
      id: current,
      title: a,
      editing: false
    });
  }

  addCatagory(): void {
    var current = this.sortableList.length;
    this.sortableList.push({
      id: current + 1,
      title: 'Catagory ' + (current + 1),
      editing: true
    });
  }

  delCatagory(a): void {
    var pos = this.sortableList
      .map(function(e) {
        return e.title;
      })
      .indexOf(a);
    this.sortableList.splice(pos, 1);
  }

  spliceCategory(a): void {
    var value = a;
    var current = this.sortableList.length;
    console.log('Value ' + value);
    console.log('Current ' + current);
  }

  editCategory(a): void {
    var value = a;
    var current = this.sortableList.length;
    this.sortableList.push({
      id: current + 1,
      title: 'Catagory ' + (current + 1),
      editing: false
    });
  }

  constructor() {}

  ngOnInit() {}

  ngAfterViewInit() {}
}

Error

ERROR TypeError: "_co.box is undefined"
View_CategoriesComponent_5ng:///AppModule

/CategoriesComponent.ngfactory.js:28:11
handleEventhttp://localhost:4200/vendor.js:41342:16
callWithDebugContexthttp://localhost:4200/vendor.js:42435:22
debugHandleEventhttp://localhost:4200/vendor.js:42138:12
dispatchEventhttp://localhost:4200/vendor.js:38801:16
renderEventHandlerClosurehttp://localhost:4200/vendor.js:39245:38
decoratePreventDefaulthttp://localhost:4200/vendor.js:51364:36
invokeTaskhttp://localhost:4200/polyfills.js:2743:17
onInvokeTaskhttp://localhost:4200/vendor.js:34899:24
invokeTaskhttp://localhost:4200/polyfills.js:2742:17
runTaskhttp://localhost:4200/polyfills.js:2510:28
invokeTaskhttp://localhost:4200/polyfills.js:2818:24
invokeTaskhttp://localhost:4200/polyfills.js:3862:9
globalZoneAwareCallbackhttp://localhost:4200/polyfills.js:3888:17 
CategoriesComponent.html:35:10
1
Sorry for asking but is the selector in the JS code correct? Perhaps, you could confirm this by using jQuery or pure JavaScript. - acarlstein
This could be happening because the ngIf is inside a ngFor. I am not sure how Angular handles template variables like #box inside ngFor. It could be that the context that has the variable is lost inside ngIf. The documentation for Angular isn't clear about this sort of thing. Can you do something like [(ngModel)]="row.categoryName" and then use that value from the row object? - Reactgular
hm. I tried that and it came up Can't bind to 'ngModel' since it isn't a known property of 'input'. - Anthony Agnone

1 Answers

0
votes

I had this problem and if I am not mistaken: the problem is when sending data with inputs they need to have unique IDs to be distinct one from another during the different operations.

Since the inputs are created with *ngFor, you can put a generic name in the ID attribute and append the index value of the *ngFor:

<tbody dragula="categories" [(dragulaModel)]="sortableList" id="tbody">
          <tr *ngFor="let row of sortableList; index as i;">
            <th scope="row" class="sort-cell">
              <button class="btn bg-transparent">
                <img src="/assets/images/sortable.svg" alt="">
              </button>
            </th>
            <td>
              <p *ngIf="!row.editing">
               {{row.title}}
              </p>

              <input #box placeholder="Category Name" id="CategoryName{{i}}">

            </td>
            <!-- ... -->
          </tr>
        </tbody>

All input IDs will start with the same characters and have a unique number at the end, thus making them unique:

<input #box placeholder="Category Name" id="CategoryName0">
<input #box placeholder="Category Name" id="CategoryName1">
...
<input #box placeholder="Category Name" id="CategoryNameN">