I've got the following child component with a form and I need to display error message under my control when parent component submit main form.
import {
Component, ChangeDetectorRef, forwardRef,
NgModule, OnInit
} from '@angular/core';
import {
FormGroup, FormControl, FormBuilder, Validators,
NG_VALIDATORS, Validator, AbstractControl, ValidationErrors
} from '@angular/forms';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
export const ADDRESS_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AddressFormComponent),
multi: true
};
export const ADDRESS_VALIDATORS: any = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => AddressFormComponent),
multi: true
};
@Component({
selector: 'app-address-form',
templateUrl: 'address-form.component.html',
providers: [ADDRESS_VALUE_ACCESSOR, ADDRESS_VALIDATORS],
})
export class AddressFormComponent implements OnInit, ControlValueAccessor, Validator {
addressForm: FormGroup;
private innerValue: any;
onModelTouched: Function = () => { };
onModelChange: Function = () => { };
constructor(
private _fb: FormBuilder,
private ref: ChangeDetectorRef,
) { }
ngOnInit() {
this.addressForm = this._fb.group({
'via': new FormControl('', Validators.required),
'civico': new FormControl(''),
'cap': new FormControl(''),
'comune': new FormControl(''),
'provincia': new FormControl(''),
'regione': new FormControl(''),
'stato': new FormControl(''),
'frazione': new FormControl('')
});
}
// get accessor
get value(): any {
return this.innerValue;
}
// set accessor including call the onchange callback
set value(v: any) {
if (v !== this.innerValue) {
this.innerValue = v;
this.onModelChange();
}
}
// Set touched on blur
onBlur() {
this.onModelTouched();
}
writeValue(value: any): void {
this.value = value;
if (this.value != null) {
this.addressForm.get('via').setValue(this.value.via);
this.addressForm.get('civico').setValue(this.value.civico);
this.addressForm.get('cap').setValue(this.value.cap);
this.addressForm.get('comune').setValue(this.value.comune);
this.addressForm.get('provincia').setValue(this.value.provincia);
this.addressForm.get('frazione').setValue(this.value.frazione);
this.addressForm.get('stato').setValue(this.value.stato);
this.addressForm.get('regione').setValue(this.value.regione);
}
this.ref.markForCheck();
}
registerOnChange(fn: Function): void {
this.addressForm.valueChanges.subscribe(() => {
fn(this.addressForm.value);
});
this.onModelChange = fn;
}
registerOnTouched(fn: Function): void {
this.onModelTouched = fn;
}
validate(c: AbstractControl): ValidationErrors | null {
return this.addressForm.valid ? null : { subformerror: 'Problems in subform!' };
}
registerOnValidatorChange(fn: () => void): void {
this.addressForm.statusChanges.subscribe(fn);
}
}
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
],
exports: [AddressFormComponent],
declarations: [
AddressFormComponent
],
entryComponents: [AddressFormComponent],
providers: [
]
})
export class AddressFormModule { }
View:
<form [formGroup]="addressForm">
<div class="row">
<div class="form-group col-md-10">
<label for="txtVia">Via</label>
<input type="text" pInputText class="form-control" id="txtVia"
formControlName="via">
<div *ngIf="!addressForm.controls['via'].valid && (addressForm.controls['via'].dirty || addressForm.controls['via'].touched)"
class="alert alert-danger">
Campo obbligatorio
</div>
</div>
<div class="form-group col-md-2">
<label for="txtCivico">Civico</label>
<input type="text" pInputText class="form-control" id="txtCivico"
formControlName="civico">
</div>
</div>
<div class="row">
<div class="form-group col-md-3">
<label for="txtCap">Cap</label>
<input type="text" pInputText class="form-control" id="txtCap"
formControlName="cap">
</div>
<div class="form-group col-md-6">
<label for="txtComune">Comune</label>
<input type="text" pInputText class="form-control" id="txtComune"
formControlName="comune">
</div>
<div class="form-group col-md-3">
<label for="txtProvincia">Provincia</label>
<input type="text" pInputText class="form-control" id="txtProvincia"
formControlName="provincia">
</div>
</div>
<div class="form-group">
<label for="txtFrazione">Frazione</label>
<input type="text" pInputText class="form-control" id="txtFrazione"
formControlName="frazione">
</div>
<div class="row">
<div class="form-group col-md-6">
<label for="txtRegione">Regione</label>
<input type="text" pInputText class="form-control" id="txtRegione"
formControlName="regione">
</div>
<div class="form-group col-md-6">
<label for="txtStato">Stato</label>
<input type="text" pInputText class="form-control" id="txtStato"
formControlName="stato">
</div>
</div>
</form>
I bind it to a parent form this way:
view:
<form (submit)="onConfirm($event)" *ngIf="aziendaform" [formGroup]="aziendaform">
<app-address-form formControlName="indirizzoSpedizione"></app-address-form>
</form>
code:
import { Component, Input, Output, EventEmitter, OnInit, SimpleChanges, OnChanges, ChangeDetectorRef } from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms';
@Component({
selector: 'app-azienda-form',
templateUrl: 'aziende-form.component.html'
})
export class AziendeFormComponent implements OnInit, OnChanges {
aziendaform: FormGroup;
constructor(
private fb: FormBuilder,
private cdr: ChangeDetectorRef
) {
}
ngOnChanges(changes: SimpleChanges) {
}
ngOnInit(): void {
this.aziendaform = this.fb.group({
'indirizzoSpedizione': new FormControl(null)
});
this.cdr.detectChanges();
}
validateAllFormFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAllFormFields(control);
}
});
}
onConfirm(event) {
event.preventDefault();
if (this.aziendaform.valid) {
/*
do somithing
*/
} else {
this.validateAllFormFields(this.aziendaform);
}
}
}
When I press confirm button the form is invalid but in the child component there are no error messages.
It seems that markAsTouched
does not propagate to child form.
Can someone help me?
update
I prepared a StackBlitz here: https://stackblitz.com/edit/angular-4qcsox
As you can see confirm and reset button does not affect child component.