Skip to content

Decide whether the ng-untouched NG0100 reports are a real defect or a sampling artifact, so the check can be enforced #572

Description

@Aman-Mittal

What is happening

The second of the two NG0100 shapes the e2e suite reports. Observed on a real-backend run:

[change-detection] _GLAccountFormComponent, _FinancialActivityMappingFormComponent
  on https://localhost:4200/tellers/1/cashiers/1/transactions
  - NG0100: Previous value for 'ng-untouched': 'true'. Current value: 'false'.
      at NgControlStatus_HostBindings
  - NG0100: Previous value for 'ng-untouched': 'true'. Current value: 'false'.
      at NgControlStatusGroup_HostBindings

The same shape appears for _LoginComponent on most tests, since every test signs in.

Unlike the two-way binding problem in the companion issue, it is not established that this one is an application defect at all, and that question should be answered before anyone writes a fix.

Why it might not be a defect

e2e/fixtures.ts already explains the mechanism:

The interval check samples at arbitrary moments, so it also catches states that are briefly inconsistent and then settle — reactive form validity is the one still outstanding […] enforcing today costs between zero and seventeen unrelated failures depending on timing.

app.config.ts runs checkNoChanges exhaustively on an interval. A control becomes touched when the user blurs it; if the interval fires between Angular's two passes during that interaction, the status legitimately differs between them and the error says so. That would make this an artifact of how the check samples, not a bug in the form.

Against that: a genuinely inconsistent state, even a brief one, is what [attr.aria-invalid] and the validation styling read. If a control's status can change without a change-detection pass, the error message a user sees can lag the control's real state.

Two corrections to what is currently recorded

1. It is not specific to reactive forms. The note in fixtures.ts attributes this to "reactive form validity". NgControlStatus's selector is [formControlName],[ngModel],[formControl], and gl-account-form.component.ts — one of the two components in the report above — uses FormsModule and [(ngModel)], not ReactiveFormsModule. Only 6 components in the application use reactive forms at all. Whatever the cause is, it reaches template-driven forms too.

2. The reported URL is wrong. The fixture accumulates errors for the whole test and prints page.url() when it flushes, so the URL is wherever the test finished, not where the error happened. That is why _GLAccountFormComponent appears above under a /tellers/… URL — an accounting form is not on that screen. Anyone chasing these from the log will start in the wrong place.

This is a small fix worth making first, since it makes the rest of the investigation possible: capture page.url() alongside each error as it arrives rather than once at the end.

What to do

  1. Fix the attribution in e2e/fixtures.ts so each error carries the URL it occurred on.
  2. Determine the cause for one component — gl-account-form is the smallest. Is the status changing during a render pass, or is the interval simply sampling mid-interaction? Disabling the interval check locally and reproducing by hand is the quickest discriminator.
  3. Then either fix the components, or — if it is a sampling artifact — narrow what the fixture treats as a failure, so that ENFORCE_CD_ERRORS=1 can be turned on by default. Record the conclusion either way; the current note in fixtures.ts is a reasonable guess that has not been tested and is partly wrong.

Business value

The point is the check, not these particular messages. fixtures.ts says it plainly:

The value is in the reporting: this is what identified the six empty-dropdown components, all now fixed. Flip the default once the remaining sources are gone.

An advisory check that always prints something is one people stop reading. With the two-way bindings in the companion issue fixed and this question answered, ENFORCE_CD_ERRORS=1 becomes the default and the next change-detection regression fails a pull request instead of reaching a user.

Reproducing

npx playwright test --project=mocked e2e/functional-coverage.spec.ts 2>&1 | grep -A6 '\[change-detection\]'
ENFORCE_CD_ERRORS=1 npx playwright test --project=mocked e2e/functional-coverage.spec.ts

The backend project shows it too, but needs a Fineract stack; the mocked project does not.

Scope

In scope: the URL attribution fix, the diagnosis, and whichever of the two outcomes follows.

Out of scope: the 275 [(ngModel)] bindings — those are a separate, already-understood defect.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions