我的模板中有以下按钮:
<button type="button" class="mgmButton" (click)="onSave()" [disabled]="saveDisabled()">Save</button>
根据saveDisabled函数的结果禁用该按钮。
saveDisabled(): boolean {
this.validationMessage = '';
for (var i = 0; i < this.tableData.length; i++) {
let row = this.tableData[i];
if (row.edit) {
if (row.data.roleCode == null || row.data.roleCode == '' ||
row.data.grantProgramCode == null || row.data.grantProgramCode == '') {
this.validationMessage = 'Row ' + (i + 1) + ' has not filled in all required fields. ';
}
}
}
if(this.validationMessage == '') {
return false;
} else {
return true;
}
该函数的早期版本没有构建validationMessage,它只返回true或false。这没有任何错误。但是当我将validationMessage属性添加到方法/组件/模板时,我开始得到“表达式在被检查后已经更改”异常。
基于其他帖子,看起来会发生这种情况,因为我在更改检测仍然发生时更改了validationMessage变量。我不确定我是完全理解发生了什么,也不知道摆脱错误的最佳方法。
更新
我创建了一个自定义验证器,它几乎完美无缺。
我的组件有一个名为tableData的数据数组。 tableData中的每一行都是一个对象,它在模板的html表中显示为一行。有时一行处于只读模式,有时数据处于编辑模式,因此行中的某些列是输入字段,选择下拉列表等。
自定义验证程序应用于表单标记。它将tableData作为输入。我的所有验证逻辑都有效,如果验证器返回错误,我会在模板中显示它。 (我确实必须将tableData转换为json字符串,然后解析它以使组件和验证器之间的切换正常工作。)
但是时间似乎存在问题。让我们说给定的行处于编辑模式,用户更改选择菜单的值。此选择绑定到tableData的一行中的属性。触发了表单中的验证,但传入的数据具有select的旧值,而不是新值。本质上,表单的验证发生在表行上的数据绑定更新后备对象之前。
答案 0 :(得分:9)
我通过从angular core添加ChangeDetectionStrategy来解决。
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'page1',
templateUrl: 'page1.html',
})
答案 1 :(得分:1)
维尔曼塔斯解释得非常好。您正在更改绑定(validationMessage
)而不会触发新一轮的更改检测,从而导致错误。它可以作为警告,validationMessage
的当前值可能不会反映在用户界面中,直到将来出现 触发变更检测的事情。
要修复,只需在更新验证消息后手动触发更改检测:
import { ChangeDetectorRef } from '@angular/core';
export class Whatever {
constructor(private cdr: ChangeDetectorRef) {}
myMethod() {
// do stuff here
this.cdr.detectChanges(); // detect changes
}
}
Angular确实对表单有很好的内置支持,但我确实认为这样会更好。您现在看到的此代码似乎遍历每个表行的整个tableData数组,并且在更改检测期间它将运行多次。至少,您可能希望将i
作为参数传递给它(它在ngFor
循环中可用)并保存for
循环。
答案 2 :(得分:0)
我对angular2内部结构并不太熟悉,但我想它是这样的:
validationMessage
出现在模板上的Save
按钮之前。validationMessage
没有值。saveDisabled()
,然后更改validatonMessage
值。validationMessage
的值在一个渲染/检查周期内发生了变化。要解决此问题,最好将验证代码移出saveDisabled()
方法并将布尔结果存储到字段中,或者甚至可以使用相同的validationMessage
来禁用该按钮:
<button type="button" ... [disabled]="validationMessage">Save</button>
(空或空字符串将计算为false,非空消息 - 为true)
尝试将代码放在tableData
被修改的位置。
但是,如果您的tableData
是某种形式,带有验证器(标准或自定义)的标准angular2形式很可能会更好。
答案 3 :(得分:0)
@Vilmantas正确描述了正在发生的事情。他没有说的是这种方法一般是不正确的。它应该是相反的方式 - 禁用标志应该根据有效标志计算,该标志在变化检测开始之前已经计算,而不是同时开始计算。
Angular 2为此任务提供了特殊的验证机制:接口验证器,您可以在自定义指令中实现,并且此相同的指令可以将自身提供为NG_VALIDATORS令牌的多提供程序。以下是我的一个项目的例子。
此外,验证器可以是异步的(NG_ASYNC_VALIDATORS令牌的提供者),这也是内置选项。在这种情况下,您可以考虑“挂起”标志来绘制一些待处理验证的指示,例如通过调用某些Web服务来检查用户名可用性。
将此验证器提供给ngModel后,您可以使用带有值的标准ngModel.errors映射,或者使用ngModel.valid标志(以及其他标志 - 还有其中一些标志)来确定是否存在任何错误并启用/禁用基于它的控件。使用这种方法,您甚至不需要考虑何时执行 - angular将为您处理所有事情。
当然,你必须组织这个东西,以便它可以在模板上下文中使用,我的意思是在某些模块/组件和这样的东西中正确导入/导出。
import {Directive, forwardRef} from '@angular/core';
import {Validator, AbstractControl, NG_VALIDATORS} from '@angular/forms';
import {IDateValidationResult} from './date-validator.interfaces';
import {moment} from '../../../shared/moment';
const DATE_VALIDATOR = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => DateValidatorDirective),
multi: true
};
@Directive({
selector:
'[whatever-selector-you-want]',
providers: [DATE_VALIDATOR]
})
export class DateValidatorDirective implements Validator {
validate = (control: AbstractControl): IDateValidationResult => {
if (!control) {
return {required: true};
}
if (control.value === undefined) {
return {required: true};
}
if (control.value === null) {
return {invalidFormat: true};
}
let m = moment(control.value);
if (!m.isValid()) {
return {invalidFormat: true};
}
return null;
}
}
模板:
<input type="date" [(ngModel)]="myModel" whatever-selector-you-want #myDateCtrl="ngModel">
<div *ngIf="myDateCtrl.invalid">
<div *ngIf="myDateCtrl.errors.required">The date is required</div>
<div *ngIf="myDateCtrl.errors.invalidFormat">Date format is invalid</div>
</div>