title: Angular FormGroup custom validator not triggered on button click
body: <p>I am using reactive forms Angular 4 and added a custom validator addressValidation to the form group - addressGroup.
   I am updating all fields to mark as touched on submit click. Looks like the custom validator addressValidation doesn't trigger eventhough I marked all fields as touched. I tried marking the formgroup (addressGroup) as touched and dirty on submit but no help. </p>
  
  <p>In general what I am trying to achieve is - By default I want to make street number and Street name required. If po box is entered then street number and Name is not required. Apt # is only required only if street number and name is entered. I am trying to achieve this on the custom validator in the formGroup.</p>
  
  <p>Any idea on what I am doing wrong. Any other alternate way to achieve the above requirement. I am new to Angular and slowly learning the concepts. Any suggestion on How to trigger the custom validator on submit.</p>
  
  <pre><code>     buildForm(): void {
              this.contactForm = this.fb.group({
                  emailAddressControl: ['', [Validators.required, Validators.email, Validators.maxLength(100)]],
                  phoneControl: ['', [Validators.required, Validators.minLength(10), Validators.maxLength(10)]],            
                  addressGroup: this.fb.group({
                      streetNumber: ['', [Validators.maxLength(10)]],
                      pOBox: ['', [Validators.maxLength(8)]],
                      aptNumber: ['', [Validators.maxLength(8)]],
                      streetName: ['', [Validators.maxLength(60)]],
                      cityControl: ['', [Validators.required, Validators.maxLength(50)]],
                      stateControl: ['', [Validators.required, Validators.maxLength(2)]],
                      zipControl: ['', [Validators.required, Validators.maxLength(14)]],
                      countryControl: ['UNITED STATES OF AMERICA', [Validators.required]],
                  }, { validator: addressValidation })
              })
              this.contactForm.valueChanges
                  .debounceTime(800)
                  .subscribe(data =&gt; this.onValueChanged(data));
              this.onValueChanged();
          }
  
  
      onSubmit(): void {
  
  
              this.markAllFormFieldsAsTouched(this.contactForm);
              this.onValueChanged();
  
      }
  
     private markAllFormFieldsAsTouched(formGroup: FormGroup) {
          Object.keys(formGroup.controls).forEach(field =&gt; {
              console.log(field);
              const control = formGroup.get(field);
              if (control instanceof FormControl) {
                  control.markAsTouched({ onlySelf: true });
              }
              else if (control instanceof FormGroup) {
                  this.markAllFormFieldsAsTouched(control);
                  control.markAsTouched({ onlySelf: true });
              }
              else if (control instanceof FormArray) {
                  for (let formgroupKey in control.controls) {
                      let formgroup = control.controls[formgroupKey];
                      if (formgroup instanceof FormGroup) {
                          this.markAllFormFieldsAsTouched(formgroup);
                      }
                  }
              }
  
          });
      }
  
      function addressValidation(c: AbstractControl): { [key: string]: boolean } | null {
          if (c.pristine) {
              return null;
          }
          const pOBoxControl = c.get('pOBox');
          const streetNameControl = c.get('streetName');
          const streetNumberControl = c.get('streetNumber');
          const aptNumberControl = c.get('aptNumber');
          if (pOBoxControl.value === null || pOBoxControl.value === "") {
              if (streetNumberControl.value === null || streetNumberControl.value === "") {
                  return { ['streetNumberRequired']: true, ['streetNameRequired']: true };
              }
              if (streetNameControl.value === null || streetNameControl.value === "") {
                  return { 'streetNameRequired': true };
              }
          }
          else {
              if ((streetNameControl.value === null || streetNameControl.value === "")
                  &amp;&amp; (streetNameControl.value === null || streetNumberControl.value === "") &amp;&amp; aptNumberControl.value !== "") {
                  return { 'apartmentNumberInvalid': true };
              }
          }
      }
  </code></pre>
  
  <p>Template</p>
  
  <pre><code> &lt;div class="card"&gt;
              &lt;div class="card-header bg-info text-white"&gt;
                  &lt;h2&gt;Mailing Address:&lt;/h2&gt;
              &lt;/div&gt;
              &lt;div formGroupName="addressGroup" class="card-body"&gt;
                  &lt;div class="row"&gt;
                      &lt;div class="col-lg-4"&gt;
                          &lt;div class="form-group"&gt;
                              &lt;label class="form-control-label"&gt;PO Box:&lt;/label&gt;
                              &lt;input class="form-control"
                                     [ngClass]="displayFieldCss('pOBox')"
                                     type="text"
                                     formControlName="pOBox"
                                     placeholder=""
                                     maxlength="8" /&gt;
                              &lt;span class="invalid-feedback" *ngIf="isValidToDisplayErrors('pOBox')"&gt;
                                  {{validationMessage.pOBox}}
                              &lt;/span&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;
                      &lt;div class="col-lg-4"&gt;
                          &lt;div class="form-group"&gt;
                              &lt;label class="form-control-label"&gt;Street Number:&lt;/label&gt;
                              &lt;input class="form-control"
                                     [ngClass]="displayFieldCss('streetNumber')"
                                     type="text"
                                     formControlName="streetNumber"
                                     placeholder=""
                                     maxlength="10" /&gt;
                              &lt;span class="invalid-feedback" *ngIf="isValidToDisplayErrors('streetNumber')"&gt;
                                  {{validationMessage.streetNumber}}
                              &lt;/span&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;
                      &lt;div class="col-lg-4"&gt;
                          &lt;div class="form-group"&gt;
                              &lt;label class="form-control-label"&gt;Apt Number:&lt;/label&gt;
                              &lt;input class="form-control"
                                     [ngClass]="displayFieldCss('aptNumber')"
                                     type="text"
                                     formControlName="aptNumber"
                                     placeholder=""
                                     maxlength="8" /&gt;
                              &lt;span class="invalid-feedback" *ngIf="isValidToDisplayErrors('aptNumber')"&gt;
                                  {{validationMessage.aptNumber}}
                              &lt;/span&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;                    
                  &lt;/div&gt;
                  &lt;div class="row"&gt;
                      &lt;div class="col-lg-12"&gt;
                          &lt;div class="form-group"&gt;
                              &lt;label class="form-control-label"&gt;Street Name:&lt;/label&gt;
                              &lt;input class="form-control"
                                     [ngClass]="displayFieldCss('streetName')"
                                     type="text"
                                     formControlName="streetName"
                                     placeholder=""
                                     maxlength="60" /&gt;
                              &lt;span class="invalid-feedback" *ngIf="isValidToDisplayErrors('streetName')"&gt;
                                  {{validationMessage.streetName}}
                              &lt;/span&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;
                  &lt;/div&gt;
                  &lt;div class="row"&gt;
                      &lt;div class="col-lg-5"&gt;
                          &lt;div class="form-group"&gt;
                              &lt;label class="form-control-label"&gt;City:&lt;/label&gt;
                              &lt;input class="form-control"
                                     [ngClass]="displayFieldCss('cityControl')"
                                     type="text"
                                     formControlName="cityControl"
                                     placeholder="(required)"
                                     maxlength="50" /&gt;
                              &lt;span class="invalid-feedback" *ngIf="isValidToDisplayErrors('cityControl')"&gt;
                                  {{validationMessage.cityControl}}
                              &lt;/span&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;
                      &lt;div class="col-lg-4"&gt;
                          &lt;div class="form-group"&gt;
                              &lt;label class="form-control-label"&gt;State/Province (Code):&lt;/label&gt;
                              &lt;input class="form-control"
                                     [ngClass]="displayFieldCss('stateControl')"
                                     type="text"
                                     formControlName="stateControl"
                                     placeholder="(required)"
                                     maxlength="3" /&gt;
                              &lt;span class="invalid-feedback" *ngIf="isValidToDisplayErrors('stateControl')"&gt;
                                  {{validationMessage.stateControl}}
                              &lt;/span&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;
                      &lt;div class="col-lg-3"&gt;
                          &lt;div class="form-group"&gt;
                              &lt;label class="form-control-label"&gt;Zip:&lt;/label&gt;
                              &lt;input class="form-control"
                                     [ngClass]="displayFieldCss('zipControl')"
                                     type="text"
                                     formControlName="zipControl"
                                     placeholder="(required)"
                                     maxlength="14" /&gt;
                              &lt;span class="invalid-feedback" *ngIf="isValidToDisplayErrors('zipControl')"&gt;
                                  {{validationMessage.zipControl}}
                              &lt;/span&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;
                  &lt;/div&gt;
                  &lt;div class="row"&gt;
                      &lt;div class="col-lg-12"&gt;
                          &lt;div class="form-group"&gt;
                              &lt;label class="form-control-label"&gt;Country:&lt;/label&gt;
                              &lt;input class="form-control"
                                     [ngClass]="displayFieldCss('countryControl')"
                                     type="text"
                                     formControlName="countryControl"
                                     placeholder="(required)"
                                     maxlength="50" /&gt;
                              &lt;span class="invalid-feedback" *ngIf="isValidToDisplayErrors('countryControl')"&gt;
                                  {{validationMessage.countryControl}}
                              &lt;/span&gt;
                          &lt;/div&gt;
                      &lt;/div&gt;
                  &lt;/div&gt;
              &lt;/div&gt;
          &lt;/div&gt;
  </code></pre>