Skip to content

fix(forms): warn in dev mode when ngModel cannot reach parent NgForm across component boundary#69585

Open
arturovt wants to merge 1 commit into
angular:mainfrom
arturovt:fix/forms_47580
Open

fix(forms): warn in dev mode when ngModel cannot reach parent NgForm across component boundary#69585
arturovt wants to merge 1 commit into
angular:mainfrom
arturovt:fix/forms_47580

Conversation

@arturovt

Copy link
Copy Markdown
Contributor

NgModel injects ControlContainer with @host(), which stops the injector at the component host element boundary. When NgForm lives in a parent component and ngModel lives in a child component, the injection returns null silently and the control acts standalone — never registering with the form.

To surface this invisible failure, emit a dev-mode warning (NG01354) when ngModel's @host() injection finds nothing but the element Injector can still reach a ControlContainer further up the hierarchy. The warning identifies the cross-boundary issue and points developers to the viewProviders fix or the standalone option.

Adds the NG01354 reference page explaining why the warning fires and providing two remediation paths: bridging ControlContainer via viewProviders, or opting out with [ngModelOptions]="{standalone: true}".

Fixes #47580

@pullapprove pullapprove Bot requested a review from JeanMeche June 30, 2026 14:28
@ngbot ngbot Bot added this to the Backlog milestone Jun 30, 2026
Comment thread adev/src/content/reference/errors/NG01354.md Outdated
await timeout();
expect(warnSpy).not.toHaveBeenCalledWith(jasmine.stringContaining('viewProviders'));
});
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we don't have a NgForm as parent but a FormGroup ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid concert, updated the code.

@JeanMeche JeanMeche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AGENT : Here are two prominent cases where the change would erroneously log a warning (false positives):

1. Explicitly standalone ngModel with [ngModelOptions]="{standalone: true}"

If a developer explicitly opts out of form registration using [ngModelOptions]="{standalone: true}", the warning will still be logged.
This occurs because the warning logic evaluates synchronously inside the NgModel constructor. During instantiation, @Host() parent resolves to null, and the constructor uses injector?.get(ControlContainer) to find the parent form and logs the warning. However, Angular @Input() bindings (such as ngModelOptions) are not initialized until after the constructor runs. The directive has no way of knowing the developer explicitly passed {standalone: true}, leading to a frustrating developer experience where the warning instructs them to apply a fix they have already applied.

2. Internal ngModel usage within a custom ControlValueAccessor

A very common pattern is creating a custom form component that implements ControlValueAccessor and using [(ngModel)] internally in its template to easily bind to the native input element.
If a user drops this custom component inside a <form>, the inner ngModel's constructor will run. Its @Host() boundary correctly stops at the custom component (parent === null), but injector.get(ControlContainer) traverses up the element injector tree and successfully finds the NgForm attached to the parent's <form> tag. The warning fires, assuming the developer accidentally disconnected the control, but the developer intentionally kept the inner ngModel isolated as an internal implementation detail of their custom component. Furthermore, because of the flaw in Case 1, the component author cannot even suppress this warning by adding standalone: true to the inner template.

…across component boundary

NgModel injects ControlContainer with @host(), which stops the injector at
the component host element boundary. When NgForm lives in a parent component
and ngModel lives in a child component, the injection returns null silently
and the control acts standalone — never registering with the form.

To surface this invisible failure, emit a dev-mode warning (NG01354) when
ngModel's @host() injection finds nothing but the element Injector can still
reach a ControlContainer further up the hierarchy. The warning identifies the
cross-boundary issue and points developers to the viewProviders fix or the
standalone option.

Adds the NG01354 reference page explaining why the warning fires and providing
two remediation paths: bridging ControlContainer via viewProviders, or opting
out with [ngModelOptions]="{standalone: true}".

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NgModel fails to register to NgForm when in a child component

3 participants