Angular FormArrays 的问题:Valuechanges、验证器和取消选择复选框
Issues with Angular FormArrays: Valuechanges, Validators and deselecting Checkboxes
我有 Angular 个 formArray 复选框。我还有一个验证器,可确保至少选中一个复选框 selected。
我的问题是:
当最后一个复选框被 select 编辑时,我想取消select 所有其他复选框。
当用户再次select任何复选框(最后一个除外)时,最后一个复选框(已经selected)必须取消select编辑。
重要提示: 用户可以 select 最后一个复选框或任意数量的其他复选框。例如,如果他 select 选择最后一个复选框,然后单击任何其他复选框,则必须取消 select 最后一个复选框。
验证器工作完美,但是当最后一个复选框被 selected 时,其他 selected 复选框不会被取消selected。
虽然我认为我做对了,但我的浏览器控制台告诉我递归太多,因此无法正常工作。
以下 link 是我的 Stackblitz 代码,您可以在其中测试功能并查看源代码。
(单击 Stackblitz 中的项目图标打开文件浏览器以查看所有文件)。
问题出在:
if (lastBook === true) {
this.deselectBooks(control);
}
你得到循环:
deselectBooks
-> 执行值更新 -> 执行订阅的验证器并再次 deselectBooks
.
不要将 false 值更新为 false。仅在需要时使用。
要到达它,请将 deselectBooks
更新为:
private deselectBooks(control: AbstractControl) {
const formArray = control as FormArray;
formArray.controls.map((book, index) => {
let bookEntry = formArray.at(index);
if(index !== (formArray.length - 1) && bookEntry.value) {
book.setValue(false)
}
});
}
和validateBooks
:
private validateBooks(control: AbstractControl, errorKey: string) {
const books = control as FormArray;
const lastBook = books.at(books.length - 1)
control.valueChanges.subscribe(value => {
control.setValidators(this.booksValidator(errorKey));
});
for(let entry of books.controls) {
if (entry !== lastBook) {
entry.valueChanges.subscribe(value => {
if(value === true && lastBook.value) {
lastBook.setValue(false);
}
});
}
}
lastBook.valueChanges.subscribe(value => {
if (value === true) {
this.deselectBooks(control);
}
});
}
https://stackblitz.com/edit/angular-6kprus?file=src/app/app.component.ts
发生这种情况是因为在检查最后一个复选框时触发了值更改,这反过来又造成了无限循环。
这是一种实现方式。
private deselectBooks(control: AbstractControl) {
const formArray = control as FormArray;
formArray.controls.map((book, index) => index !== (formArray.length - 1) ? book.setValue(false,{emitEvent:false}) : null); <--emit event as false
}
将 emitEvent 设置为 false 不会触发 valueChanges。
希望对您有所帮助!
这里的主要问题(意识到当你 setValue
时你需要 {emitEvent: false}
是你不仅需要复选框值,还需要前一个复选框值来知道哪个更改了。
这可以通过 RxJs 管道运算符实现 scan
get bookArray() {
return this.booksForm.controls['books'] as FormArray
}
this.bookArray.valueChanges.pipe(
tap((val) => console.log(val)), // e.g. [false, false, true, false, false]
distinctUntilChanged(),
scan((acc, curr) => !acc && curr[curr.length - 1], false), // True only if None of the above has been selected
tap((val) => console.log(val)), // e.g. true or false
).subscribe((lastBookSelected: Boolean)=> {
if (lastBookSelected) {
this.bookArray.controls.forEach(control => {
this.deselectBooks(control)
console.log('here')
})
} else {
// set last control false but use `{emitEvent: false}`
}
})
注意 {emitEvent: false}
是 deselectBooks
所必需的:
private deselectBooks(control: AbstractControl) {
this.bookArray.controls.map((book, index) => index !== (this.bookArray.length - 1) ? book.setValue(false, {emitEvent: false}) : null);
}
Stackblitz - https://stackblitz.com/edit/angular-tx5cpe
如果使用 pairwise 知道发生变化的复选框,可以简化问题
private validateBooks(control: AbstractControl, errorKey: string) {
control.valueChanges.pipe(startWith(control.value), pairwise()).subscribe(
([old, value]) => {
let index = -1;
value.forEach((x, i) => {
if (old[i] != x)
index = i;
})
if (index >= 0) {
if (index == value.length - 1) {
if (value[index])//if selected the last one
{
(this.booksForm.get('books') as FormArray).controls.forEach(
(c, index) => {
if (index < value.length - 2)
c.setValue(false, { emit: false })
})
}
} else {
if (value[index]){ //if has selected another
(this.booksForm.get('books') as FormArray).at(value.length - 1)
.setValue(false, { emit: false })
}
}
}
}
)
}
我有 Angular 个 formArray 复选框。我还有一个验证器,可确保至少选中一个复选框 selected。
我的问题是:
当最后一个复选框被 select 编辑时,我想取消select 所有其他复选框。
当用户再次select任何复选框(最后一个除外)时,最后一个复选框(已经selected)必须取消select编辑。
重要提示: 用户可以 select 最后一个复选框或任意数量的其他复选框。例如,如果他 select 选择最后一个复选框,然后单击任何其他复选框,则必须取消 select 最后一个复选框。
验证器工作完美,但是当最后一个复选框被 selected 时,其他 selected 复选框不会被取消selected。
虽然我认为我做对了,但我的浏览器控制台告诉我递归太多,因此无法正常工作。
以下 link 是我的 Stackblitz 代码,您可以在其中测试功能并查看源代码。
(单击 Stackblitz 中的项目图标打开文件浏览器以查看所有文件)。
问题出在:
if (lastBook === true) {
this.deselectBooks(control);
}
你得到循环:
deselectBooks
-> 执行值更新 -> 执行订阅的验证器并再次 deselectBooks
.
不要将 false 值更新为 false。仅在需要时使用。
要到达它,请将 deselectBooks
更新为:
private deselectBooks(control: AbstractControl) {
const formArray = control as FormArray;
formArray.controls.map((book, index) => {
let bookEntry = formArray.at(index);
if(index !== (formArray.length - 1) && bookEntry.value) {
book.setValue(false)
}
});
}
和validateBooks
:
private validateBooks(control: AbstractControl, errorKey: string) {
const books = control as FormArray;
const lastBook = books.at(books.length - 1)
control.valueChanges.subscribe(value => {
control.setValidators(this.booksValidator(errorKey));
});
for(let entry of books.controls) {
if (entry !== lastBook) {
entry.valueChanges.subscribe(value => {
if(value === true && lastBook.value) {
lastBook.setValue(false);
}
});
}
}
lastBook.valueChanges.subscribe(value => {
if (value === true) {
this.deselectBooks(control);
}
});
}
https://stackblitz.com/edit/angular-6kprus?file=src/app/app.component.ts
发生这种情况是因为在检查最后一个复选框时触发了值更改,这反过来又造成了无限循环。
这是一种实现方式。
private deselectBooks(control: AbstractControl) {
const formArray = control as FormArray;
formArray.controls.map((book, index) => index !== (formArray.length - 1) ? book.setValue(false,{emitEvent:false}) : null); <--emit event as false
}
将 emitEvent 设置为 false 不会触发 valueChanges。
希望对您有所帮助!
这里的主要问题(意识到当你 setValue
时你需要 {emitEvent: false}
是你不仅需要复选框值,还需要前一个复选框值来知道哪个更改了。
这可以通过 RxJs 管道运算符实现 scan
get bookArray() {
return this.booksForm.controls['books'] as FormArray
}
this.bookArray.valueChanges.pipe(
tap((val) => console.log(val)), // e.g. [false, false, true, false, false]
distinctUntilChanged(),
scan((acc, curr) => !acc && curr[curr.length - 1], false), // True only if None of the above has been selected
tap((val) => console.log(val)), // e.g. true or false
).subscribe((lastBookSelected: Boolean)=> {
if (lastBookSelected) {
this.bookArray.controls.forEach(control => {
this.deselectBooks(control)
console.log('here')
})
} else {
// set last control false but use `{emitEvent: false}`
}
})
注意 {emitEvent: false}
是 deselectBooks
所必需的:
private deselectBooks(control: AbstractControl) {
this.bookArray.controls.map((book, index) => index !== (this.bookArray.length - 1) ? book.setValue(false, {emitEvent: false}) : null);
}
Stackblitz - https://stackblitz.com/edit/angular-tx5cpe
如果使用 pairwise 知道发生变化的复选框,可以简化问题
private validateBooks(control: AbstractControl, errorKey: string) {
control.valueChanges.pipe(startWith(control.value), pairwise()).subscribe(
([old, value]) => {
let index = -1;
value.forEach((x, i) => {
if (old[i] != x)
index = i;
})
if (index >= 0) {
if (index == value.length - 1) {
if (value[index])//if selected the last one
{
(this.booksForm.get('books') as FormArray).controls.forEach(
(c, index) => {
if (index < value.length - 2)
c.setValue(false, { emit: false })
})
}
} else {
if (value[index]){ //if has selected another
(this.booksForm.get('books') as FormArray).at(value.length - 1)
.setValue(false, { emit: false })
}
}
}
}
)
}