In my Angular 4.0.2 application, I have a FormGroupcontaining a nested component that implements ControlValueAccessorand Validator. This component checks itself asynchronously. The problem is that even when the nested component becomes valid, the parent FormGroupremains invalid.
But if I just changed the validation to synchronization, the justice correctly extends to the parent group.
I threw it as far as possible to the next Plunker:
http://plnkr.co/edit/H26pqEE3sRkzKmmhrBgm
Here is the second Plunker showing a similar setup, but this time the input FormControlis directly inside FormGroup, and not part of the nested component. Here, asynchronous validation propagates correctly:
http://plnkr.co/edit/DSt4ltoO1Cw2Nx1oXxbD
Why doesn't my asynchronous validator propagate its validity when determined inside a component?
Here is the code from the first (broken) Plunker:
import {Component, NgModule, VERSION, forwardRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {FormsModule, ReactiveFormsModule} from '@angular/forms'
import {Observable} from 'rxjs/Rx'
import {
FormGroup, FormControl, FormBuilder, Validators, AbstractControl, Validator,
ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, ValidationErrors
} from '@angular/forms';
@Component({
selector: 'my-app',
template: `
<p>When validated asyncronously, why is the parent form group invalid even though its
inner control is valid?</p>
<p>Try clearing the value to see the asyncronous validation at work.</p>
<p>Try swapping line 58 for 60 to change to syncronous validation, which propagates correctly.</p>
<hr />
<div><code>FormGroup valid = {{form.valid}}</code></div><br />
<div [formGroup]="form">
<nested-control formControlName="nested"></nested-control>
</div>
<hr />
`
})
export class App {
form: FormGroup;
constructor(fb: FormBuilder) {
this.form = fb.group({
nested: ['required']
});
}
}
@Component({
selector: 'nested-control',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NestedControl),
multi: true
}, {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => NestedControl),
multi: true,
}],
template: `
<div [formGroup]="form">
<input type="text" formControlName="input" (keyup)="keyup($event)">
<code>NestedControl valid = {{form.controls.input.valid}}, errors = {{form.controls.input.errors|json}}</code>
</div>
`
})
export class NestedControl implements ControlValueAccessor, Validator {
form: FormGroup;
private propagateChange = () => {};
constructor(fb: FormBuilder) {
this.form = fb.group({
input: [null, null, this.asyncRequiredValidator]
});
}
asyncRequiredValidator(control: AbstractControl): ValidationErrors {
return Observable.interval(1000).take(1).map(_ => {
const result = Validators.required(control);
console.log('result', result);
return result;
});
}
keyup() {
this.propagateChange();
}
writeValue(v: any): void {
this.form.setValue({ input: v });
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
validate(c: AbstractControl): ValidationErrors {
return this.form.valid ? null : {invalid: true};
}
}
@NgModule({
imports: [ BrowserModule, FormsModule, ReactiveFormsModule ],
declarations: [ App, NestedControl ],
bootstrap: [ App ]
})
export class AppModule {}