ionic-team / ionic-framework

A powerful cross-platform UI toolkit for building native-quality iOS, Android, and Progressive Web Apps with HTML, CSS, and JavaScript.
https://ionicframework.com
MIT License
51k stars 13.51k forks source link

@Ionic/Angular 4.0.0 Angular forms validation status class updates not working (ng-*** != ion-***) #17414

Closed jonmikelm closed 4 years ago

jonmikelm commented 5 years ago

Bug Report

Ionic version:

4.0.0

Current behavior: Using the Angular Reactive Forms API, when markAsTouched() function is called over a FormControl, ng-touched class is applied to the ion-item element but the ion-touched class is not applied.

This problem occurs with each of the Angular Forms markAs*** functions:

(Didn't take the time to test the reset function, but I suspect that the result will be the same.)

Expected behavior: Whenever an Angular FormControl status changes ng-XXX classes applied to the DOM element are updated. ion-XXX classes should work the same way. On every FormControl status change, ion-XXX classes should also be updated.

Steps to reproduce:

  1. Change the status of a FormControl using the Angular Forms markAsXXX functions.
  2. Inspect the DOM and check the ng-XXX and ion-XXX classes applied to the corresponding ion-item.
  3. ng-XXX classes are applied, ion-XXX classes don't

Related code:

Other information:

Ionic info:

Ionic:

   ionic (Ionic CLI)             : 4.10.2 (D:\Users\****\AppData\Roaming\npm\node_modules\ionic)
   Ionic Framework               : @ionic/angular 4.0.0
   @angular-devkit/build-angular : 0.10.7
   @angular-devkit/schematics    : 7.2.3
   @angular/cli                  : 7.2.3
   @ionic/angular-toolkit        : not installed
Corneel-D commented 5 years ago

This problem also occurs for the setErrors() function. ng-valid/ng-invalid is set correspondingly, but the corresponding ionic ion-valid/ion-invalid class is not changing.

tripledrops commented 5 years ago

Seeing related issue in my project also. ng-touched class is added to ion-input when the control loses focus. That is expected. However, ion-touched is added to ion-item when ion-input receives focus.

Ionic team, please refer to Angular documentation. https://angular.io/api/forms/AbstractControl#touched

gagaXD commented 5 years ago

Hey,

I also ran into this issue using stencil, and a custom value accessor inspired by the one using by Ionic.

Because the onBlur / onChange method is not trigged by the setErrors() method, the classes are not updated => The ng-valid class is added, but the setIonClasses() is not triggered in the value accessor.

I'm trying to find a workaround, by trigerring a blur on the input, I'll let you know if I find any solution to this problem

Nightbr commented 5 years ago

Hey, I'm working with @gagaXD and here is our workaround for this.

First we created a Directive to add the nativeElement to the control:

native-element-injector.directive.ts

import { Directive, OnInit, ElementRef } from '@angular/core';
import { NgControl, AbstractControl } from '@angular/forms';

export interface BetterAbstractControl extends AbstractControl {
  nativeElement?: HTMLElement;
}

@Directive({
  // tslint:disable-next-line:directive-selector
  selector: '[formControlName]',
})
export class NativeElementInjectorDirective implements OnInit {
  constructor(private el: ElementRef, private control: NgControl) {}

  public ngOnInit() {
    (this.control.control as BetterAbstractControl).nativeElement = this.el.nativeElement as HTMLElement;
  }
}

In our forms, when we want to reset or setErrors:

  control.setErrors(null); // or control.setErrors(data);
  // blur nativeElement to force change it's State
  // @see https://github.com/ionic-team/ionic/issues/17414
  const betterControl: BetterAbstractControl = control;
  if (betterControl.nativeElement && control.touched) {
    betterControl.nativeElement.blur();
  }

In our input stencil component, we implement a blur method:

  /**
   * Method to blur manually.
   */
  @Method()
  blur() {
    if (this.nativeInput) {
      this.nativeInput.blur();
    }
  }

With this workaround we can Blur programmatically when we change programmatically the input state (with setErrors).

tripledrops commented 5 years ago

Great workaround! @Nightbr and @gagaXD Nonetheless, in my opinion, it's Ionic team's responsibility to provide its developer community with a properly working component library. I think I am going to wait for them to release a bug fix.

Nightbr commented 5 years ago

It is not trivial since they use ValueAccessor to bind with Angular framework and ValueAccessor has no hook when the state valid/invalid of the formControl is changed without changing the Value. We look but if Angular doesn't implement a Hook for this use case in the custom ValueAccessor https://angular.io/api/forms/ControlValueAccessor it will be very difficult.

Other try for us but with a drawback in performance is to listen with the MutationObserver class changes and refresh the ion-* classes. But it's not a valid solution because of the low performance of this (and potential loops...).

See:

Nightbr commented 5 years ago

Hey, just to inform that we have renamed the blur()method to setBlur() to remove the warning that stenciljs can throw.

btsiders commented 5 years ago

If the Ionic controls could add the markAs* methods and then just pass it on to the Angular controls, that would work for me. Even if that was just a stop-gap until such time when the needed Angular hooks are added...

DarkLeopard commented 4 years ago

If it will be useful to someone, I will share my workaround. I update the value of the field with its own value using the submit() method.

public submit(): void {
      if (this.form.get('confirmPassword').value !== this.form.get('password').value) {
            this.form.get('confirmPassword').setErrors({ passNotEqual: true });
            this.form.get('confirmPassword').setValue(this.form.get('confirmPassword').value);
       }
}

Input 'confirmPassword' validator:

private confirmPassword(): ValidatorFn {
    return (currentControl: AbstractControl): { [key: string]: any } => {
      if ( currentControl.parent && (currentControl.value !== currentControl.parent.get('password').value) ) {
        return { passNotEqual: true };
      } else return null;
    };
  }
nickwinger commented 4 years ago

How can it be that after 1 year there is no response from Ionic-Team ?? This is a major issue, form validation is completely not in sync with angular and the framework is useless ? When you have a (reactive) angular form and the user presses Submit, not errors are shown, validation is not working. Don't you think form validation is a key concept in nearly every App ?? I really don't get, there is not even a usefull documentation on forms on the Ionic Homepage. I don't understand how you guys can continue working on new versions without providing a framework that has the basic functionality in it...

leobaccili commented 4 years ago

So sad. I upgrade to version 5. Same problem.

liamdebeasi commented 4 years ago

Hi everyone,

Can you try the following dev build and let me know if it resolves the issue?

npm i @ionic/angular@5.2.0-dev.202006032038.ebb3f9a

The proper solution to this issue is https://github.com/angular/angular/issues/10887, but we are waiting on the Angular team to implement that. In the mean time, this dev build patches the markAs* methods to manually sync Angular's form classes with Ionic's form classes.

liamdebeasi commented 4 years ago

Thanks for the issue. This has been resolved via https://github.com/ionic-team/ionic/pull/21429, and a fix will be available in an upcoming release of Ionic Framework.

ionitron-bot[bot] commented 4 years ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.