Skip to content

Creating custom CVA based components is STILL almost impossible to in most not-super-basic scenarios (regardless the unified event api) #57972

@montella1507

Description

@montella1507

Which @angular/* package(s) are relevant/related to the feature request?

forms

Description

It is still impossible to implement working custom CVA based components in 50% of scenarios.

Main cause of this is non-existing way how to get ALL information about AbstractControl.

Why?

  • there is not any 100% working way how to get information about important control state (valid/invalid/pristine/touched).
  • it is important to know control states like valid/invalid/pristine/touched to correctly implement smarter components than hello world

Why current implementation does not work?

  • CVA interface does not provide ANY way how to get information about status changes (it can only react to ValueChanges - writevalue and disabled changes - setDisabled), there is not anything like setValid(), setTouched() pristine etc.

  • You can get ngControl from injector (because CVA is solving only 20% of domain...) .Control has now "unified events api" https://angular.dev/api/forms/AbstractControl#events unfortunately, it is preserved when {emitEvent: false} during the method calls

  • It is EVEN BIGGER Problem as far as disabled control has BOTH valid and invalid SET TO FALSE (lol), so you just DO NOT HAVE ANY way how to get any information about the state of validity, when your control is enabled with {emitEvent:false} (check source code below)

  • it is so sad that the only way how to get 100% information about control state is to LISTEN for DOM mutation on Host element (to read ng-invalid, ng-valid, ng-pending, ng-prisitine, etc. classes)

  • the same problem when validity of the control changes when the new value is set - you have NO WAY how to get the new valid / invalid status when user patch/set value with emitEvent: false. You cannot even handle this inside of writeValue() because validators are executed after that.... So not any workaround..

FORCING user not to use {emitEvent:false } which is his way to prevent emiting valueChanges "ON THE OUTSIDE SIDE" - aka. consumer of the component (in bigger form) is definitely not a solution to this problem, where Component creator has not any legit way how to get state of the control.


import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

import { FormControl, PristineChangeEvent, ReactiveFormsModule, StatusChangeEvent, TouchedChangeEvent} from '@angular/forms';
import {  bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ReactiveFormsModule, CommonModule],
  template: `
    <input type="number"  [formControl]="number"/>
    Valid: {{number.valid}}
    Invalid: {{number.invalid}}
    Errors: {{number.errors | json}}

    <button (click)="number.enable({emitEvent: false})">ENABLE</button>
    <hr/>
    Problems:
    <ul>
      <li>Disabled input has both VALID and INVALID set TRUE - eventhogh there is no validator and it NO ERRORS</li>
      <li>When you enable control - it is now valid, but NO events is fired from Control.Events</li>
    </ul>

    <hr/>
    Events: 
    <ul>
    @for(event of events; track $index) {
       <li>{{event | json}}</li>
    }
  </ul>
  `,
})
export class App {

  events: any[] =[]; 
  number = new FormControl();



  ngOnInit( ){
    this.number.disable();
    this.number.events.subscribe(e=> {
      if (e instanceof StatusChangeEvent) {
        this.events.push("Status - " + e.status);
      } else if (e instanceof TouchedChangeEvent) {
        this.events.push("Touched - "+ e.touched);
      } else if (e instanceof PristineChangeEvent) {
        this.events.push("Pristine - "+ e.pristine);
      }
    }
    );
  }
}

bootstrapApplication(App);

Proposed solution

  • more methods in CVA interface
  • event emitter on AbstractControl which emits everytime, regardless the emitEvent settings

Alternatives considered

  • our only WAY how to solve this is to write our own AbstractControl's implementations (inherit the base ones from angular) and to add additional event emiiters...
  • in this case where is the main problem the valid/invalid state of control, you can "rebind" your internal valid/invalid/pristine/touched state when setDisabled() of CVA is called.
  • in case of changed valid/invalid state when the new value is set to control with emitEvent:false, there is not any way except to "queue" sync check after "writeValue"
image

Just sad state.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions