cloudnc / ngx-sub-form

Utility library for breaking down an Angular form into multiple components
https://cloudnc.github.io/ngx-sub-form
MIT License
314 stars 33 forks source link

form group custom validators and advice #292

Closed acadianaapps closed 1 year ago

acadianaapps commented 1 year ago

This doesn't seem to be a direct ngx-sub-form issue, but I'm struggling with figuring out why the form isn't broadcasting a last key stroke change.

1st would the following code be the correct ngx-sub-form way to add a custom validator to the group itself?

(not sure it matters but the root form of this sub has ChangeDectectionStrategy.OnPush)

@Component({
  selector: 'maintanence-details-control',
  templateUrl: './maintanence-details.component.html',
  styleUrls: ['./maintanence-details.component.css'],
  providers: subformComponentProviders(MaintanenceDetailsControl)
})
export class MaintanenceDetailsControl {

  public form = createForm<MaintanenceDetails>(this, {
    formType: FormType.SUB,
    formControls: {
      collectiveOut: new FormControl(null, Validators.required),
      collectiveIn: new FormControl(null, Validators.required),
      collectiveHours: new FormControl(null, Validators.required),
      engineOut: new FormControl(null, Validators.required),
      engineIn: new FormControl(null, Validators.required),
      engineHours: new FormControl(null, Validators.required),
    },
  })

  constructor(
  ) {
    this.form.formGroup.addValidators([
      this.collectiveInHoursCantBeLessThanOutHours(this.form.formControlNames.collectiveOut, this.form.formControlNames.collectiveIn),
      this.engineInHoursCantBeLessThanOutHours,
    ]);
  }

  collectiveInHoursCantBeLessThanOutHours(_collectiveOut, _collectiveIn): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      const collectiveOut = control.get(_collectiveOut);
      const collectiveIn = control.get(_collectiveIn);
      const collectiveHourCalc = collectiveIn?.value - collectiveOut?.value;
      control.get(this.form.formControlNames.collectiveHours).setValue(Number(collectiveHourCalc) > 0 ? collectiveHourCalc.toFixed(2) : null, { onlySelf: true });
      return collectiveIn && collectiveOut && collectiveOut?.value > collectiveIn?.value ? { collectiveInHoursLessThanOut: true } : null;
    };
  }

  engineInHoursCantBeLessThanOutHours: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
    const engineOut = control.get('engineOut');
    const engineIn = control.get('engineIn');
    const engineHours = control.get('engineHours');
    const engineHourCalc = engineIn?.value - engineOut?.value;
    engineHours.setValue(engineHourCalc > 0 ? engineHourCalc.toFixed(2) : null, { onlySelf: true });
    return engineIn && engineOut && engineOut?.value > engineIn?.value ? { engineInHoursLessThanOut: true } : null;
  };

  // collectiveHoursCantBeMoreThanEngineHours: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
  //   const engineHours = control.get('engineHours');
  //   const collectiveHours = control.get('collectiveHours');
  //   return engineHours && collectiveHours && (engineHours?.value < collectiveHours?.value) ? { collectiveHoursMoreThanEngine: true } : null
  // };

}

The issues I'm running into with this is that the form group value itself is not showing the correct sum of hours. Now what gets me is the field in the view shows the correct value but the form group value is wrong. (see below screenshot) Notice the field view "Col Hours" is correct, but the form value itself "collectiveHours" is wrong.

image

Its not updating on the very last key stroke. If a user would add a zero after the last digit in a hour field it would update correctly. So how does one force an update, is this an ngx-sub-form issues, or do I need to try and watch form.formGroup.value for changes?

Thanks ahead of time

maxime1992 commented 1 year ago

Hello. Is your sub form in an invalid state while you notice this? Without checking, I think the value isn't broadcasted up if the form is invalid so that may be it

acadianaapps commented 1 year ago

@maxime1992 I'm wondering if it may be related to #93

This image shows that subForm is valid, but the engine hours remain null.

image

But if I was to do as mention in the #93 issue and just type 0 the blur out then updates the group image

I tried the suggestions in the previous issue but didn't help. adding the [data.issue-93] to the html template.

Also just FYI have refactored the component to this utilizing the "formGroupOptions" of "createForm":

import { Component } from '@angular/core';
import { AbstractControl, FormControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
import { createForm, FormType, subformComponentProviders } from 'ngx-sub-form';
import { MaintanenceDetails } from 'src/app/log/models/log.model';

@Component({
  selector: 'maintanence-details-control',
  templateUrl: './maintanence-details.component.html',
  styleUrls: ['./maintanence-details.component.css'],
  providers: subformComponentProviders(MaintanenceDetailsControl)
})
export class MaintanenceDetailsControl {

  collectiveInHoursCantBeLessThanOutHours(_collectiveOut, _collectiveIn): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      const collectiveOut = control.get(_collectiveOut);
      const collectiveIn = control.get(_collectiveIn);
      const collectiveHourCalc = collectiveIn?.value - collectiveOut?.value;
      control.get('collectiveHours').setValue(Number(collectiveHourCalc) > 0 ? collectiveHourCalc.toFixed(2) : null, { onlySelf: true });
      return collectiveIn && collectiveOut && collectiveOut?.value > collectiveIn?.value ? { collectiveInHoursLessThanOut: true } : null;
    };
  }

  engineInHoursCantBeLessThanOutHours: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
    const engineOut = control.get('engineOut');
    const engineIn = control.get('engineIn');
    const engineHours = control.get('engineHours');
    const engineHourCalc = engineIn?.value - engineOut?.value;
    engineHours.setValue(engineHourCalc > 0 ? engineHourCalc.toFixed(2) : null, { onlySelf: true });
    return engineIn && engineOut && engineOut?.value > engineIn?.value ? { engineInHoursLessThanOut: true } : null;
  };

  collectiveHoursCantBeMoreThanEngineHours: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
    const engineHours = control.get('engineHours');
    const collectiveHours = control.get('collectiveHours');
    return engineHours && collectiveHours && (engineHours?.value < collectiveHours?.value) ? { collectiveHoursMoreThanEngine: true } : null
  };

  public form = createForm<MaintanenceDetails>(this, {
    formType: FormType.SUB,
    formGroupOptions: {
      updateOn: 'blur',
      validators: [
        this.collectiveInHoursCantBeLessThanOutHours('collectiveOut', 'collectiveIn'),
        this.engineInHoursCantBeLessThanOutHours,
        this.collectiveHoursCantBeMoreThanEngineHours,
      ]
    },
    formControls: {
      collectiveOut: new FormControl(null, Validators.required),
      collectiveIn: new FormControl(null, Validators.required),
      collectiveHours: new FormControl(null, Validators.required),
      engineOut: new FormControl(null, Validators.required),
      engineIn: new FormControl(null, Validators.required),
      engineHours: new FormControl(null, Validators.required),
    },
  })

}
maxime1992 commented 1 year ago

Can you please try to build a minimal repro on Stackblitz. Just necessary fields etc to keep it as simple as possible

acadianaapps commented 1 year ago

Hopefully this helps

Stackblitz Example

maxime1992 commented 1 year ago

Ok so thanks for the repro! That said, it's definitely not a minimal repro. A minimal repro consist of trying to create a dead simple repro with only the strict minimum to help pin point the issue by making a work upstream of removing everything that's not needed in the demo. There's load of context in yours.

I've still taken a look. I suspect, it might come from the fact that you're computing a field in the form and updating it through a validator. That really sounds like where the issue might be and it seems completely unrelated to ngx sub form.

I'd strongly encourage you to have computed values outside of the form if possible and otherwise as a first step not update the value in the validator.

I'll have to close this issue as it's not ngx sub form related and I don't have much more time to investigate. Try out the above and otherwise ask on stackoverflow I reckon.

If you do ask on stackoverflow, I'd strongly encourage you to just create one simple form, no ngx-sub-form at all, pure angular. And try to reproduce this with only the needed fields and minimal amount of code needed. If you manage to do that you'll probably get some help on SO :)

If it happens to be ngx sub form related feel free to reopen though!

acadianaapps commented 1 year ago

@maxime1992 Thanks for your time and great library.