使用 <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);
}
}
我完全怀疑这种行为的发生是因为 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>
您也可以通过从每个输入字段中删除 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:我正在研究为什么当 name
和 input
组合时它的工作方式不同,我怀疑 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>
如果此解决方案适合您,请告诉我!
我在组件中有一组对象。我将在模板中迭代。
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);
}
}
我完全怀疑这种行为的发生是因为 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>
您也可以通过从每个输入字段中删除 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:我正在研究为什么当 name
和 input
组合时它的工作方式不同,我怀疑 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>
如果此解决方案适合您,请告诉我!