I've created a Plunker here - https://plnkr.co/edit/1Aztv5K2gqIc4erNgRnG?p=preview
TL;DR
To see the problem you should open the developer tools window and look at the console log output. Then go to the <input>
box and repeatedly add some text and then clear it. You will see the value of errors
output to the console, but those errors never appear in the component's view.
I've written a custom component that implements NG_VALUE_ACCESSOR
to give my control a databinding context, and NG_VALIDATORS
so I can have access to the data source (AbstractControl) being validated.
I cannot see why the *ngFor
in my component's template is not listing the errors that I can see are present by looking in the console.log output.
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 {
}
}
It doesn't seem to be a change detection problem, as I have also tried implementing this using Observables and the async pipe and still it shows no errors.
The consumer code looks like this
//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 {}