taiga-ui icon indicating copy to clipboard operation
taiga-ui copied to clipboard

🐞 - `FieldErrorPipe` change detection problem with `touched`-state

Open nsbarsukov opened this issue 2 years ago • 6 comments

Which @taiga-ui/* package(s) are the source of the bug?

kit

Please provide a link to a minimal reproduction of the bug

See description

Is this issue blocking you?

Non-Blocking

Description

Preconditions: Typescript-file (pay attention what happens in constructor):

const LONG_TEXT_EXAMPLE = `
In Java: everything is an object.
In Clojure: everything is a list.
In JavaScript: everything is a terrible mistake.
`;

@Component({
    selector: 'tui-text-area-example-4',
    templateUrl: './index.html',
    changeDetection,
    encapsulation,
    styleUrls: ['./index.less'],
})
export class TuiTextAreaExample4 {
    readonly maxLength = 97;

    readonly testForm = new FormGroup({
        testValue1: new FormControl(LONG_TEXT_EXAMPLE.trim(), [
            Validators.required,
            Validators.maxLength(this.maxLength),
        ]),
    });

    constructor() {
        this.testForm.markAllAsTouched();
    }
}

Problem description:

  1. Use deprecated <tui-field-error />:
<form
    class="form tui-col_md-6"
    [formGroup]="testForm"
>
    <label
        tuiLabel
        label="Write a wise thought"
        class="tui-row"
    >
        <tui-text-area
            formControlName="testValue1"
            tuiHintContent="it's just a joke"
            [expandable]="true"
            [tuiTextfieldMaxLength]="maxLength"
            [tuiTextfieldLabelOutside]="true"
        >
            Type it
        </tui-text-area>
        <tui-field-error formControlName="testValue1"></tui-field-error>
    </label>
</form>
before Everything works fine!
  1. Use new pipe FieldError
<form
    class="form tui-col_md-6"
    [formGroup]="testForm"
>
    <label
        tuiLabel
        label="Write a wise thought"
        class="tui-row"
    >
        <tui-text-area
            formControlName="testValue1"
            tuiHintContent="it's just a joke"
            [expandable]="true"
            [tuiTextfieldMaxLength]="maxLength"
            [tuiTextfieldLabelOutside]="true"
        >
            Type it
        </tui-text-area>
        <tui-error
            formControlName="testValue1"
            [error]="[] | tuiFieldError | async"
        ></tui-error>
    </label>
</form>

You need to trigger change detection (for example, to hover over a text-area) to show error message.

Angular version

any

Taiga UI version

2.48.0

Which browsers have you used?

  • [X] Chrome
  • [ ] Firefox
  • [ ] Safari
  • [ ] Edge

Which operating systems have you used?

  • [X] macOS
  • [ ] Windows
  • [ ] Linux
  • [ ] iOS
  • [ ] Android

nsbarsukov avatar Jun 15 '22 07:06 nsbarsukov

Probably, it is impossible to fix until fixing this issue: https://github.com/angular/angular/issues/10887#issuecomment-421289798

Deprecated FieldError component uses Default-change detection strategy (that is why it works). Our new pipe requires observable with changes of touched-status.

nsbarsukov avatar Jun 15 '22 08:06 nsbarsukov

What do you think about

constructor(
        @Optional()
        @Self()
        @Inject(NgControl)
        private readonly ngControl: NgControl | null,
       // ...
) {
  if (this.ngControl?.control?.markAllAsTouched) {
            const markAllAsTouched = this.ngControl.control.markAllAsTouched;

            this.ngControl.control.markAllAsTouched = () => {
                markAllAsTouched();
                this.cd.markForCheck();
            }
        }
}

splincode avatar Jun 15 '22 09:06 splincode

Another not perfect solution:

export class TuiFieldErrorPipe implements PipeTransform {
  private readonly isTouched$ = this.refresh$.pipe(
          throttleTime(200),
          startWith(null),
          map(() => this.touched),
          distinctUntilChanged(),
          tuiZonefree(this.ngZone),
      );
  // ...
  constructor(
          @Inject(NgZone) private readonly ngZone: NgZone,
          @Inject(ANIMATION_FRAME) private readonly refresh$: Observable<unknown>,
  ) {
  
  // ...
  transform(order: readonly string[]): Observable<TuiValidationError | null> {
          this.order = order;
  
          return this.isTouched$.pipe(switchMap(() => this.computedError));
  }
  // ...
}

nsbarsukov avatar Jun 15 '22 09:06 nsbarsukov

@vladimirpotekhin @waterplea let's discuss)

nsbarsukov avatar Jun 15 '22 09:06 nsbarsukov

We have utility function markControlAsTouchedAndValidate. Would it help? Let's not make messy workarounds.

waterplea avatar Jun 15 '22 16:06 waterplea

Utility markControlAsTouchedAndValidate does not help(

nsbarsukov avatar Jun 20 '22 14:06 nsbarsukov

fixed by https://github.com/Tinkoff/taiga-ui/pull/3349

splincode avatar Jan 19 '23 13:01 splincode