我正在创建一个包含输入字段的自定义表单控件组件(电子邮件),我转发输入字段的值(正确完成),但也希望转发错误。
使用以下代码,当输入发生变化时,我成功传递了字段的错误,但没有正确地检测到初始错误。
例如,即使在视图完全加载并且值为' example@email.com'之后,此电子邮件字段仍会报告错误= {' required':true}。被传递给它。开始输入字段,它将正确传递错误。
所以我的问题是,如何在初始加载数据后传递错误?
注意:运行this.propagateChange(this.value)可以解决问题;在DoCheck的生命周期中,我不喜欢它,我需要更有效的东西,但似乎没有其他钩子可以做到这一点。
以下是示例:
import {Component, forwardRef, Input, ViewChild} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, NgModel} from '@angular/forms';
@Component({
selector: 'input-email',
template:`<form-group
[errors]="{'required':'Email required', 'email':'Invalid email format'}"
[info]="'Email*'"
>
<input
type = "email"
name = "email"
class = "form-control"
[(ngModel)] = "value"
placeholder = "{{placeholder}}"
(input)="onChange()"
email
required
#f = "ngModel"
>
{{f.errors | json}}
</form-group>`,
styleUrls: ['./email.component.css'],
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => InputEmailComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => InputEmailComponent), multi: true }
]
})
export class InputEmailComponent implements ControlValueAccessor {
value:String = null;
@ViewChild('f') f:NgModel;
@Input()
placeholder:String = "Email";
propagateChange:any = (val) => {};
constructor() {}
onChange(){
this.propagateChange(this.value);
}
/**
* Write a passed NgValue value to the element.
*/
writeValue(value) {
if (value && this.value != value) {
this.value = value;
}
}
/**
* Set the function to be called
* when the control receives a change event.
* registers 'fn' that will be fired when changes are made
* this is how we emit the changes back to the form
*/
registerOnChange(fn) {
this.propagateChange = fn;
}
/**
* Set the function to be called
* when the control receives a touch event.
*/
registerOnTouched(fn) {}
/**
* Set the function to be called
* to validate if input has errors.
*/
validate(c: FormControl):any {
console.log('validate email');
return this.f.errors;
}
}
答案 0 :(得分:1)
使用NG_ASYNC_VALIDATORS而不是NG_VALIDATORS可以解决问题, 我在这里发布了适合我的解决方案:
function getElementsByClassName(a, b) { var c = []; var d = a.getElementsByTagName("*"); for (var e = 0; e < d.length; e++) { if (d[e].className.indexOf(b) != -1) { c.push(d[e]) } } return c } function DisableDates(a) { if (a._element.className.indexOf("disable_past_dates") != -1) { var b = getElementsByClassName(a._days, "ajax__calendar_day"); var c = (new Date).setHours(0, 0, 0, 0); for (var d = 0; d < b.length; d++) { try { if (b[d].date.setHours(0, 0, 0, 0) >= c) { b[d].className = "ajax__calendar_day" } else { b[d].className = "ajax__calendar_day ajax__calendar_day_disabled" } } catch (e) { } } } if (a._element.className.indexOf("disable_future_dates") != -1) { var b = getElementsByClassName(a._days, "ajax__calendar_day"); var c = (new Date).setHours(0, 0, 0, 0); for (var d = 0; d < b.length; d++) { try { if (b[d].date.setHours(0, 0, 0, 0) <= c) { b[d].className = "ajax__calendar_day" } else { b[d].className = "ajax__calendar_day ajax__calendar_day_disabled" } } catch (e) { } } } } AjaxControlToolkit.CalendarBehavior.prototype.show = function (a) { this._ensureCalendar(); if (!this._isOpen) { var b = new Sys.CancelEventArgs; this.raiseShowing(b); if (b.get_cancel()) { return } this._isOpen = true; this._popupBehavior.show(); if (this._firstPopUp) { this._switchMonth(null, true); switch (this._defaultView) { case AjaxControlToolkit.CalendarDefaultView.Months: this._switchMode("months", true); break; case AjaxControlToolkit.CalendarDefaultView.Years: this._switchMode("years", true); break } this._firstPopUp = false } this.raiseShown(); DisableDates(this) } }; AjaxControlToolkit.CalendarBehavior.prototype._cell_onclick = function (a) { a.stopPropagation(); a.preventDefault(); if (!this._enabled) return; var b = a.target; var c = this._getEffectiveVisibleDate(); Sys.UI.DomElement.removeCssClass(b.parentNode, "ajax__calendar_hover"); switch (b.mode) { case "prev": case "next": this._switchMonth(b.date); break; case "title": switch (this._mode) { case "days": this._switchMode("months"); break; case "months": this._switchMode("years"); break } break; case "month": if (b.month == c.getMonth()) { this._switchMode("days") } else { this._visibleDate = b.date; this._switchMode("days") } break; case "year": if (b.date.getFullYear() == c.getFullYear()) { this._switchMode("months") } else { this._visibleDate = b.date; this._switchMode("months") } break; case "day": if (this._element.className.indexOf("disable_past_dates") != -1) { if (b.date.setHours(0, 0, 0, 0) >= (new Date).setHours(0, 0, 0, 0)) { this.set_selectedDate(b.date); this._switchMonth(b.date); this._blur.post(true); this.raiseDateSelectionChanged() } } else if (this._element.className.indexOf("disable_future_dates") != -1) { if (b.date.setHours(0, 0, 0, 0) <= (new Date).setHours(0, 0, 0, 0)) { this.set_selectedDate(b.date); this._switchMonth(b.date); this._blur.post(true); this.raiseDateSelectionChanged() } } else { this.set_selectedDate(b.date); this._switchMonth(b.date); this._blur.post(true); this.raiseDateSelectionChanged() } break; case "today": this.set_selectedDate(b.date); this._switchMonth(b.date); this._blur.post(true); this.raiseDateSelectionChanged(); break } DisableDates(this) };
答案 1 :(得分:0)
由于问题FormGroup & FormControl statusChanges are not emitted on creation #14542,我在采用您的提案时遇到了麻烦。
因此,我进行了同步。验证器是这样的:
export class MyInputComponent implements AfterViewInit, ControlValueAccessor, Validator {
...
@ViewChild('datePicker') public datePickerInput: NgbInputDatepicker;
...
ngAfterViewInit(): void {
this.ngControl = this.injector.get(NgControl);
// Force restart of validation
if (this.ngControl && this.ngControl.control) {
this.ngControl.control.updateValueAndValidity({
onlySelf: true
});
}
}
...
public validate(control: AbstractControl): ValidationErrors | null {
return this.datePickerInput ? this.datePickerInput.validate(control) : null;
}
答案 2 :(得分:0)
我通过在onValidatorChange
中调用Validator
回调(来自ngAfterViewInit
接口)来解决此问题:
ngAfterViewInit(): void {
// Force the validation to trigger after the view has been initialised
// otherwise the initial value is not marked as invalid
if (this.onValidatorChange) {
// you need a setTimeout here to make the change after Angular's change detection
setTimeout(() => {
this.onValidatorChange();
this.cdr.markForCheck();
}, 0);
}
}
registerOnValidatorChange(fn: () => void): void {
this.onValidatorChange = fn
}