Angular 反应形式的条件验证
conditional validation of Angular reactive forms
我是 angular 的新手,正在研究反应式。
我有一个 html table,通过循环生成控件
我想根据以下情况添加验证
- 当此页面加载时,默认情况下应禁用“保存”按钮(这是我通过使用
[disabled]="!myform.valid"
实现的
- 只有当用户在任何文本框或特定行的 select 复选框中输入值时,保存按钮才应启用。
复选框 select 和在文本框中添加值不应允许用户。
复选框应 selected 或者用户可以在任何文本框中输入值。
我试过这个来实现
IsFormValid(){return (!!this.myfm.get('myform').value);}// but this returning always true.
这是我的代码
myfm:FormGroup;
ngOnInit() {
debugger;
this.myfm = this.fb.group({
myform: this.fb.array([this.addformFileds()])
});
}
addformFileds(): FormGroup {
return this.fb.group({
NoTask: ['', Validators.required],// only check box is required either checkbox should click
or enter value in any text-boxes
task1: ['', null],
task2: ['', null],
task3: ['', null],
task4: ['', null],
task5: ['', null],
task6: ['', null],
task7: ['', null],
task8: ['', null],
task9: ['', null],
task10: ['', null],
task11: ['', null],
task12: ['', null]
});
}
//adding more rows on click of Add More Rows button
addEntried():void{
this.idAddNewRow=true; //indicator that new rows is being created
(<FormGroup>this.myfm.get('myform')).push(this.addEntried());
}
我知道这是否有点棘手,但仍然没有找到解决方案。这对我很有帮助。
我的component.html代码
<form #frmImp='NgForm' [formGroup]="myfm"]>
<div class="modal-content">
<div class="modal-header" style="align-items: center">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<!--<table class="table-bordered table-responsive" style="overflow-x: true;"> to disable the scroll bar if screen resolution is less than 1920x1080-->
<table class="table-bordered " >
<thead>
<tr style="border-bottom-style: solid"><td id="" class="" colspan="15" style="text-align: center"><b>Imp Task Details</b></td>
</tr>
<tr>
<th style="text-align: center;"> </th>
<th style="text-align: center;"> </th>
<th style="text-align: center;">No Task</th>
<th style="text-align: center;">Task 1</th>
<th style="text-align: center;">Task 2</th>
<th style="text-align: center;">Task 3</th>
<th style="text-align: center;">Task 4</th>
<th style="text-align: center;">Task 5</th>
<th style="text-align: center;">Task 6</th>
<th style="text-align: center;">Task 7</th>
<th style="text-align: center;">Task 8</th>
<th style="text-align: center;">Task 9</th>
<th style="text-align: center;">Task 10</th>
<th style="text-align: center;">Task 11</th>
<th style="text-align: center;">Task 12</th>
</tr>
</thead>
<tbody formArrayName="myform" **ngFor="let frm of myfm.get('myform')['controls']; let i=index">
<tr [[formGroupName]="i"]>
<td></td>
<td></td>
<td><input [id]="'txt'+i" type="checkbox" formControlName="NoTask" style="margin-left: 30px;"/></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task1" placeholder="0"></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task2" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task3" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task4" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task5" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task6" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task7" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task8" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task9" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task10" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task11" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task12" ></td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" *ngIf="isAvailable" (click)="addformFileds()">Add More Rows </button>
<button type="submit" class="btn btn-primary" (click)=SaveImpDetails(frmImp)>Save </button>
</div>
</div>
</form>
**component.ts** file code
SaveImpDetails(){
this.baseSrvc.post(ApiResource.SaveImpDetails,
JSON.stringify(body)).subscribe((result=>{
if(result){
this.alertService.showMessage("Success!", "Details has been
recorded
successfuly.")
}if(result.isError){
this.alert.showMessage("Error!! while saving details");
}
}));
}
更新:
理解了整个问题后,我们可以通过为每个表单项添加一个新的验证器来解决它。
这里是复制示例(你可以在 Stackblitz 上看到相同的):
myfm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() { this._generateForm(); }
private _generateForm() {
this.myfm = this.fb.group({
myform: this.fb.array([this.addformFileds()]),
});
}
addformFileds(): FormGroup {
return this.fb.group(
{
NoTask: '',
task1: '',
task2: '',
task3: '',
task4: '',
task5: '',
task6: '',
task7: '',
task8: '',
task9: '',
task10: '',
task11: '',
task12: '',
},
{ validator: [validateRow()] }
);
}
辅助函数和验证器
function validateRow(): ValidatorFn {
const pairs: string[][] = [
['task1', 'task2'],
['task3', 'task4'],
['task5', 'task6'],
['task7', 'task8'],
['task9', 'task10'],
['task11', 'task12'],
];
return (group: FormGroup): ValidationErrors | null => {
if (group.get('NoTask').value) {
return null;
}
// check the pair validities
const invalidPair = pairs.find((pair) => {
const firstTask = group.get(pair[0]).value;
const secondTask = group.get(pair[1]).value;
if ((firstTask && !secondTask) || (!firstTask && secondTask)) {
return true;
}
return false;
});
// return pair validity error object
if (invalidPair) {
return { invalidPair };
}
if (!singleWhere(group, (item) => item.value)) {
return {invalidRow: true};
}
return null;
};
}
/**
* find and return the first control for which
* the `callback` function returns `true`
*
* loop over the `group` controls
* and return the `control`
* if the `callback` function returns `true` for the `control`
*
* @param group - is the form
* @param callback - is the callback function
*
* @return `AbstractControl`
*/
function singleWhere(
group: AbstractControl,
callback: (control: AbstractControl, index: number) => boolean
): AbstractControl {
if (!group) {
return null;
}
if (group instanceof FormControl) {
return callback(group, 0) ? group : null;
}
const keys = Object.keys((group as FormGroup).controls);
for (let i = 0; i < keys.length; i++) {
const control = group?.get(keys[i]);
if (singleWhere(control, callback)) {
return group;
}
}
return null;
}
最后,在模板中,通过此条件添加禁用属性
[disabled]="myfm.invalid || myfm.untouched"
这里是复制示例
https://stackblitz.com/edit/angular-bootstrap-4-starter-vvimke?file=src/app/app.component.ts
为什么不对任务使用 FormArray?由于您有一个 formArray 并且在 formArray 内部,您应该使用两个函数:
//you can defined your formArray like
formArray=this.fb.array([0,1,2,3,4,5,6,7].map(_=>this.addformFields()))
getGroup(i)
{
return this.formArray.at(i) as FormGroup
}
tasks(i)
{
return this.getGroup(i).get('tasks') as FormArray
}
你的函数 addformFields 变得像
addformFields(): FormGroup {
return this.fb.group({
noTask:false,
tasks:this.fb.array([0,1,2,3,4,5,6,7,8,9,10,11].map(_=>this.fb.control(0)))
},{validator:this.someValue()})
}
验证器就像
someValue(){
return (control:AbstractControl)=>{
const group=control as FormGroup;
let valid=control.get('noTask').value;
(control.get('tasks') as FormArray).controls.forEach(x=>{
valid=valid || +x.value!=0
})
return valid?null:{error:"You shoul indicate one task or that there' not task"}
}
}
.html来控制FormArray
<table class="form-group">
<tr>
<th>Section</th>
<th *ngFor="let i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]">Task {{ i }}</th>
</tr>
<tbody>
<tr
*ngFor="let group of formArray.controls; let i = index"
[formGroup]="getGroup(i)"
>
<td><input type="checkbox" formControlName="noTask" /></td>
<ng-container formArrayName="tasks">
<td *ngFor="let control of tasks(i).controls; let j = index">
<input
[attr.disabled]="getGroup(i).get('noTask').value ? true : null"
[formControlName]="j"
/>
</td>
</ng-container>
</tr>
</tbody>
</table>
看看,如果,例如你定义了一个 .css like
tr.ng-invalid.ng-touched input{
border:1px solid red;
}
tr.ng-invalid.ng-touched input:focus{
border-color: red;
border-radius: .25rem;
border-width: 2px;
outline: 0;
box-shadow: 0 0 0 .15rem rgba(255,0,0,.25);
}
我是 angular 的新手,正在研究反应式。
我有一个 html table,通过循环生成控件
我想根据以下情况添加验证
- 当此页面加载时,默认情况下应禁用“保存”按钮(这是我通过使用
[disabled]="!myform.valid"
实现的
- 只有当用户在任何文本框或特定行的 select 复选框中输入值时,保存按钮才应启用。 复选框 select 和在文本框中添加值不应允许用户。 复选框应 selected 或者用户可以在任何文本框中输入值。
我试过这个来实现
IsFormValid(){return (!!this.myfm.get('myform').value);}// but this returning always true.
这是我的代码
myfm:FormGroup;
ngOnInit() {
debugger;
this.myfm = this.fb.group({
myform: this.fb.array([this.addformFileds()])
});
}
addformFileds(): FormGroup {
return this.fb.group({
NoTask: ['', Validators.required],// only check box is required either checkbox should click
or enter value in any text-boxes
task1: ['', null],
task2: ['', null],
task3: ['', null],
task4: ['', null],
task5: ['', null],
task6: ['', null],
task7: ['', null],
task8: ['', null],
task9: ['', null],
task10: ['', null],
task11: ['', null],
task12: ['', null]
});
}
//adding more rows on click of Add More Rows button
addEntried():void{
this.idAddNewRow=true; //indicator that new rows is being created
(<FormGroup>this.myfm.get('myform')).push(this.addEntried());
}
我知道这是否有点棘手,但仍然没有找到解决方案。这对我很有帮助。
我的component.html代码
<form #frmImp='NgForm' [formGroup]="myfm"]>
<div class="modal-content">
<div class="modal-header" style="align-items: center">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<!--<table class="table-bordered table-responsive" style="overflow-x: true;"> to disable the scroll bar if screen resolution is less than 1920x1080-->
<table class="table-bordered " >
<thead>
<tr style="border-bottom-style: solid"><td id="" class="" colspan="15" style="text-align: center"><b>Imp Task Details</b></td>
</tr>
<tr>
<th style="text-align: center;"> </th>
<th style="text-align: center;"> </th>
<th style="text-align: center;">No Task</th>
<th style="text-align: center;">Task 1</th>
<th style="text-align: center;">Task 2</th>
<th style="text-align: center;">Task 3</th>
<th style="text-align: center;">Task 4</th>
<th style="text-align: center;">Task 5</th>
<th style="text-align: center;">Task 6</th>
<th style="text-align: center;">Task 7</th>
<th style="text-align: center;">Task 8</th>
<th style="text-align: center;">Task 9</th>
<th style="text-align: center;">Task 10</th>
<th style="text-align: center;">Task 11</th>
<th style="text-align: center;">Task 12</th>
</tr>
</thead>
<tbody formArrayName="myform" **ngFor="let frm of myfm.get('myform')['controls']; let i=index">
<tr [[formGroupName]="i"]>
<td></td>
<td></td>
<td><input [id]="'txt'+i" type="checkbox" formControlName="NoTask" style="margin-left: 30px;"/></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task1" placeholder="0"></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task2" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task3" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task4" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task5" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task6" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task7" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task8" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task9" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task10" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task11" ></td>
<td ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task12" ></td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" *ngIf="isAvailable" (click)="addformFileds()">Add More Rows </button>
<button type="submit" class="btn btn-primary" (click)=SaveImpDetails(frmImp)>Save </button>
</div>
</div>
</form>
**component.ts** file code
SaveImpDetails(){
this.baseSrvc.post(ApiResource.SaveImpDetails,
JSON.stringify(body)).subscribe((result=>{
if(result){
this.alertService.showMessage("Success!", "Details has been
recorded
successfuly.")
}if(result.isError){
this.alert.showMessage("Error!! while saving details");
}
}));
}
更新:
理解了整个问题后,我们可以通过为每个表单项添加一个新的验证器来解决它。
这里是复制示例(你可以在 Stackblitz 上看到相同的):
myfm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() { this._generateForm(); }
private _generateForm() {
this.myfm = this.fb.group({
myform: this.fb.array([this.addformFileds()]),
});
}
addformFileds(): FormGroup {
return this.fb.group(
{
NoTask: '',
task1: '',
task2: '',
task3: '',
task4: '',
task5: '',
task6: '',
task7: '',
task8: '',
task9: '',
task10: '',
task11: '',
task12: '',
},
{ validator: [validateRow()] }
);
}
辅助函数和验证器
function validateRow(): ValidatorFn {
const pairs: string[][] = [
['task1', 'task2'],
['task3', 'task4'],
['task5', 'task6'],
['task7', 'task8'],
['task9', 'task10'],
['task11', 'task12'],
];
return (group: FormGroup): ValidationErrors | null => {
if (group.get('NoTask').value) {
return null;
}
// check the pair validities
const invalidPair = pairs.find((pair) => {
const firstTask = group.get(pair[0]).value;
const secondTask = group.get(pair[1]).value;
if ((firstTask && !secondTask) || (!firstTask && secondTask)) {
return true;
}
return false;
});
// return pair validity error object
if (invalidPair) {
return { invalidPair };
}
if (!singleWhere(group, (item) => item.value)) {
return {invalidRow: true};
}
return null;
};
}
/**
* find and return the first control for which
* the `callback` function returns `true`
*
* loop over the `group` controls
* and return the `control`
* if the `callback` function returns `true` for the `control`
*
* @param group - is the form
* @param callback - is the callback function
*
* @return `AbstractControl`
*/
function singleWhere(
group: AbstractControl,
callback: (control: AbstractControl, index: number) => boolean
): AbstractControl {
if (!group) {
return null;
}
if (group instanceof FormControl) {
return callback(group, 0) ? group : null;
}
const keys = Object.keys((group as FormGroup).controls);
for (let i = 0; i < keys.length; i++) {
const control = group?.get(keys[i]);
if (singleWhere(control, callback)) {
return group;
}
}
return null;
}
最后,在模板中,通过此条件添加禁用属性
[disabled]="myfm.invalid || myfm.untouched"
这里是复制示例 https://stackblitz.com/edit/angular-bootstrap-4-starter-vvimke?file=src/app/app.component.ts
为什么不对任务使用 FormArray?由于您有一个 formArray 并且在 formArray 内部,您应该使用两个函数:
//you can defined your formArray like
formArray=this.fb.array([0,1,2,3,4,5,6,7].map(_=>this.addformFields()))
getGroup(i)
{
return this.formArray.at(i) as FormGroup
}
tasks(i)
{
return this.getGroup(i).get('tasks') as FormArray
}
你的函数 addformFields 变得像
addformFields(): FormGroup {
return this.fb.group({
noTask:false,
tasks:this.fb.array([0,1,2,3,4,5,6,7,8,9,10,11].map(_=>this.fb.control(0)))
},{validator:this.someValue()})
}
验证器就像
someValue(){
return (control:AbstractControl)=>{
const group=control as FormGroup;
let valid=control.get('noTask').value;
(control.get('tasks') as FormArray).controls.forEach(x=>{
valid=valid || +x.value!=0
})
return valid?null:{error:"You shoul indicate one task or that there' not task"}
}
}
.html来控制FormArray
<table class="form-group">
<tr>
<th>Section</th>
<th *ngFor="let i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]">Task {{ i }}</th>
</tr>
<tbody>
<tr
*ngFor="let group of formArray.controls; let i = index"
[formGroup]="getGroup(i)"
>
<td><input type="checkbox" formControlName="noTask" /></td>
<ng-container formArrayName="tasks">
<td *ngFor="let control of tasks(i).controls; let j = index">
<input
[attr.disabled]="getGroup(i).get('noTask').value ? true : null"
[formControlName]="j"
/>
</td>
</ng-container>
</tr>
</tbody>
</table>
看看,如果,例如你定义了一个 .css like
tr.ng-invalid.ng-touched input{
border:1px solid red;
}
tr.ng-invalid.ng-touched input:focus{
border-color: red;
border-radius: .25rem;
border-width: 2px;
outline: 0;
box-shadow: 0 0 0 .15rem rgba(255,0,0,.25);
}