angular / angular/angular

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

Open
#57,972 7 comments 0 reactions 0 assignees View on GitHub
area: forms
Dominant language
TypeScript
Stars
101k
Forks
27.5k
Avg merge
1d 19h
Merged PRs (30d)
288

Description

### 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: `

Valid: {{number.valid}}
Invalid: {{number.invalid}}
Errors: {{number.errors | json}}

ENABLE



Problems:

  • Disabled input has both VALID and INVALID set TRUE - eventhogh there is no validator and it NO ERRORS

  • When you enable control - it is now valid, but NO events is fired from Control.Events



Events:

    @for(event of events; track $index) {
  • {{event | json}}

  • }

`,
})
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.

Contributor guide

Open the contributing guide

Research direction

Start with the provided reproduction and inspect the Angular forms CVA interface, AbstractControl state, and AbstractControl.events behavior around disable/enable and emitEvent:false. Compare the proposed CVA methods and unconditional AbstractControl event emitter with existing forms APIs; done should be defined by tests covering control validity and state visibility in custom CVA components.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, typescript
Domain
frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.