我在这里创建了一个Plunker - https://plnkr.co/edit/1Aztv5K2gqIc4erNgRnG?p=preview
TL; DR
要查看问题,您应该打开开发人员工具窗口并查看控制台日志输出。然后转到<input>
框并重复添加一些文本然后清除它。您将看到errors
输出到控制台的值,但这些错误永远不会出现在组件的视图中。
我编写了一个实现NG_VALUE_ACCESSOR
的自定义组件来为我的控件提供数据绑定上下文,并NG_VALIDATORS
这样我就可以访问正在验证的数据源(AbstractControl)。
我无法理解为什么我的组件模板中的*ngFor
没有通过查看console.log输出列出我能看到的错误。
import {Component, NgModule, forwardRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {
AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors,
Validator
} from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/add/operator/distinctUntilChanged';
@Component({
selector: 'validation-errors',
template: `
<div>Errors should appear below this line</div>
<div *ngFor='let item of errors'>{{ item }}!!!</div>
`,
providers: [
{
provide: NG_VALIDATORS,
useClass: forwardRef(() => ValidationErrorsComponent),
multi: true
},
{
provide: NG_VALUE_ACCESSOR,
useClass: forwardRef(() => ValidationErrorsComponent),
multi: true
}
]
})
export class ValidationErrorsComponent implements Validator, ControlValueAccessor {
public errors: string[] = [];
private control: AbstractControl = null;
private updateErrors(errorsObject: any) {
this.errors = [];
if (errorsObject) {
for (const errorType of Object.keys(errorsObject)) {
this.errors.push(errorType);
}
}
console.log('Errors: ' + JSON.stringify(this.errors));
}
validate(c: AbstractControl): ValidationErrors | any {
if (this.control === null && c !== null && c !== undefined) {
this.control = c;
this.control.statusChanges
.distinctUntilChanged()
.subscribe(x => this.updateErrors(this.control.errors));
}
}
writeValue(obj: any): void {
}
registerOnChange(fn: any): void {
}
registerOnTouched(fn: any): void {
}
}
它似乎不是一个更改检测问题,因为我也尝试使用Observables和异步管道来实现它,但它仍然没有显示任何错误。
消费者代码如下所示
//our root app component
import {Component, NgModule, forwardRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import {
AbstractControl, ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors,
Validator
} from '@angular/forms';
import { ValidationErrorsComponent } from './validation-errors.component';
@Component({
selector: 'my-app',
template: `
<div [formGroup]='form'>
<input formControlName='name' style="width: 100%"/>
<validation-errors formControlName='name'></validation-errors>
</div>
`,
})
export class App {
public form: FormGroup;
constructor(formBuilder: FormBuilder) {
this.form = formBuilder.group({
name: ['Delete this text to trigger required vaidation error', Validators.required]
})
}
}
@NgModule({
imports: [ BrowserModule, FormsModule, ReactiveFormsModule ],
exports: [
FormsModule,
ReactiveFormsModule,
ValidationErrorsComponent
],
declarations: [ App, ValidationErrorsComponent ],
bootstrap: [ App ]
})
export class AppModule {}
答案 0 :(得分:1)
更新:问题是正在创建3个组件实例。一个用于模板中的标记,一个用于NG_VALIDATORS
提供程序,另一个用于NG_VALUE_ACCESSOR
提供程序。这是因为我在提供者声明中指定了useClass而不是useExisting。您可以使用原始代码,但我认为通过将此指令添加到<input>
可以更好地实现,因此它可以共享其formControlName
。
我已经恢复了我的旧代码,该代码由一个创建验证器实例的指令组成。对于任何希望实现相同目标的人来说,这是源头。请注意,我创建了ValidationError的实例,这只是一个简单的类。
/**
* Used to denote a validation error of some kind
*
* @class ValidationError
*/
export class ValidationError {
/**
* @constructor
* @param (string) message Key to the translation of the text to display
* @param parameters Any additional parameters (max-length, etc)
*/
constructor(public message: string, public parameters: any) {}
}
以下是该指令的来源:
import { ComponentFactoryResolver, Directive, forwardRef, OnDestroy, OnInit, ViewContainerRef } from '@angular/core';
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ValidationError } from '../../validation-error';
import { ValidationErrorsComponent } from '../../components/validation-errors/validation-errors.component';
@Directive({
selector:
'[formControl][showValidationErrors], ' +
'[formControlName][showValidationErrors], ' +
'[ngModel][showValidationErrors]',
providers: [
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ShowValidationErrorsDirective), multi: true
}
]
})
export class ShowValidationErrorsDirective implements Validator, OnInit, OnDestroy {
errors: Observable<ValidationError[]>;
private isSubscribedToControl = false;
private isDestroyed = false;
private validationErrorsComponent: ValidationErrorsComponent;
private errorsSubject: BehaviorSubject<ValidationError[]>;
constructor(
private viewContainerRef: ViewContainerRef,
private componentFactoryResolver: ComponentFactoryResolver
) {
this.errorsSubject = new BehaviorSubject([]);
this.errors = Observable.from(this.errorsSubject);
}
ngOnInit() {
const factory = this.componentFactoryResolver.resolveComponentFactory(ValidationErrorsComponent);
const componentReference = this.viewContainerRef.createComponent(factory);
this.validationErrorsComponent = componentReference.instance;
this.validationErrorsComponent.errors = this.errors;
}
validate(control: AbstractControl): ValidationErrors | any {
this.subscribeToControlErrors(control);
return null; // We haven't added any errors
}
private subscribeToControlErrors(control: AbstractControl) {
if (!this.isSubscribedToControl) {
this.isSubscribedToControl = true;
control.statusChanges
.takeWhile(x => !this.isDestroyed)
.distinctUntilChanged()
.map(x => control.errors)
.subscribe(x => this.populateErrors(x));
}
}
private populateErrors(errorsObject: any) {
const errors = [];
if (errorsObject) {
for (const errorType of Object.keys(errorsObject)) {
errors.push(new ValidationError(errorType, errorsObject[errorType]));
}
}
this.errorsSubject.next(errors);
}
registerOnValidatorChange(fn: () => void): void {
}
ngOnDestroy(): void {
this.isDestroyed = true;
}
}
以下是组件的模板:
<div *ngFor="let error of errors | async">
{{ error.message | translate }}
</div>
组件的来源:
import { Component, Input } from '@angular/core';
import { ValidationError } from '../../validation-error';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'validation-errors',
templateUrl: './validation-errors.component.html',
styleUrls: ['./validation-errors.component.scss'],
})
export class ValidationErrorsComponent {
@Input()
errors: Observable<ValidationError[]>;
}
最后,这就是它在消费者中的使用方式:
<input formControlName="mobileNumber" showValidationErrors />