使用 <form> 对数组元素进行两种方式的绑定

Two way binding on Array elements using <form>

我在组件中有一组对象。我将在模板中迭代。

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'sample-app';
  classesData = [];

  constructor() {
  }

  ngOnInit() {
    this.classesData = [
      {title: 'Hello0'}, {title: 'Hello1'}, {title: 'Hello2'}
    ];
  }

  duplicate() {
    const newData = JSON.parse(JSON.stringify(this.classesData[1]));
    newData.title += 'Copy';
    this.classesData.splice(1, 0, newData);
  }
}

app.template.html

<form #testingFrom="ngForm">
  <p>{{classesData | json}}</p>
  <div *ngFor="let classData of classesData; let i=index">
    <input [(ngModel)]="classData.title" name="{{'title-' + i}}" type="text">
  </div>
  <button (click)="duplicate()">Duplicate</button>
</form>

我的目标是当用户点击 duplicate 按钮时,我只需在数组中的 index 1 处添加一个新元素。我的初始状态看起来像(在用户点击之前)

用户点击复制按钮后我的状态

在上图中的第 3 个输入字段中,我们得到的是 Hello1Copy 而不是 Hello1.

问题是您正在使用表单。因为您使用的是表单,所以如果您打算 更改 现有源,则需要指定 angular 应如何跟踪表单项的更改。您可以使用 trackBy 管道来执行此操作:

<form #testingFrom="ngForm">
  <p>{{classesData | json}}</p>
  <div *ngFor="let classData of classesData; let i=index; trackBy: trackByFn">
    <input [(ngModel)]="classData.title" [name]="'title-' + i" type="text">
  </div>
  <button (click)="duplicate()">Duplicate</button>
</form>

打字稿相关部分:

  trackByFn(index: any) {
    return index;
  }

请注意,向集合添加 元素将适用于您的原始示例。

工作 stackblitz:https://stackblitz.com/edit/angular-uabuya

创建另一个变量并迭代该变量以创建输入

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

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  title = 'sample-app';
  originalData=[];
  classesData = [];

  constructor() {
  }

  ngOnInit() {
    this.classesData = [
      {title: 'Hello0'}, {title: 'Hello1'}, {title: 'Hello2'}
    ];
    this.originalData=[...this.classesData]; // changed here
  }

  duplicate() {
    const newData = JSON.parse(JSON.stringify(this.classesData[1]));
    newData.title += 'Copy';
    this.classesData.splice(1, 0, newData);
  }
}

Working Demo

我完全怀疑这种行为的发生是因为 name 属性值发生冲突。仅对于这种情况,如果您在第一个位置 splice newItem,它只会添加该变量,而其他 DOM 不会重新呈现。对于交叉验证,您可以尝试用 {{classData.title}} 等简单绑定替换 input 元素,一切正常。

此行为可以通过始终不冲突 name 属性值轻松解决。这意味着为每个收集项目分配一个唯一的 id 变量并使用它。

this.classesData = [
  { id: 1, title: 'Hello0' }, 
  { id: 2, title: 'Hello1' }, 
  { id: 3, title: 'Hello2' }
];
duplicate() {
    const newData = JSON.parse(JSON.stringify(this.classesData[1]));
    newData.title += 'Copy';
    newData.id = Date.now()
    this.classesData.splice(1, 0, newData);
}

模板

<div *ngFor="let classData of classesData;let i=index">
   <input [(ngModel)]="classData.title" [name]="'title_'+classData.id" type="text">
</div>

Stackblitz


您也可以通过从每个输入字段中删除 name 属性来进行验证。但这还不够,它会抛出

ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions.

因此在每个输入字段上添加 [ngModelOptions]="{standalone: true}" 以使输入在没有 name 属性的情况下也能正常工作。正如@briosheje 在另一个答案中所建议的那样,您还可以使用 trackBy.

重新强制渲染

PS:我正在研究为什么当 nameinput 组合时它的工作方式不同,我怀疑 form API用 input 元素接线。我会尽快更新答案。

您可以使用 "trackBy" 功能解决您的问题。请参阅下面的代码示例。

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'sample-app';
  classesData = [];

  constructor() {}

  ngOnInit() {
    this.classesData = [
      { title: 'Hello0' },
      { title: 'Hello1' },
      { title: 'Hello2' }
    ];
  }

  duplicate() {
    const newData = JSON.parse(JSON.stringify(this.classesData[1]));
    newData.title += 'Copy';
    this.classesData.splice(1, 0, newData);
  }

  trackByIndex(index: number, obj: any): any {
    return index;
  }
}

app.component.html

<form>
  <p>{{classesData | json}}</p>
  <div *ngFor="let classData of classesData; let i=index;trackBy:trackByIndex;">
    <input [(ngModel)]="classesData[i].title" name="{{'title-' + i}}" type="text" />
  </div>
  <button (click)="duplicate()">Duplicate</button>
</form>

如果此解决方案适合您,请告诉我!