Fix: Can't Bind to 'formGroup' in Angular (Known Property)
The 'Can't bind to formGroup' error occurs when Angular doesn't recognize the formGroup directive. Here's how to fix it in both NgModule and standalone setups.
Photo by Chris Ried on Unsplash
Angular's compiler stops the build with this exact message the moment it meets <form [formGroup]="myForm"> in a template it can't resolve:
Can't bind to 'formGroup' since it isn't a known property of 'form'.
If 'formGroup' is a directive, ensure it is included in the @NgModule imports or standalone component imports.The cause is almost always a missing ReactiveFormsModule import — either in your NgModule or in the imports array of a standalone component. The behavior is the same across all Angular versions ≥ v2 — it's not version-specific, just configuration-specific. Where the import belongs, however, depends on your setup, and that's where the three common scenarios diverge:
- a lazy-loaded module where
ReactiveFormsModuleisn't imported - a standalone component that doesn't list
ReactiveFormsModulein itsimportsarray - a shared module that declares a form component but forgets to re-export
ReactiveFormsModule
Why the compiler rejects [formGroup]#
The formGroup directive is not part of Angular's core runtime — it's provided by the @angular/forms package and only becomes available when ReactiveFormsModule is imported. Angular's Ahead-of-Time (AOT) compiler enforces strict template type checking: if a directive isn't declared in the current module's imports array (or the standalone component's imports), the compiler treats the binding as an unknown property.
This is intentional — it prevents accidental use of directives that may not be available in production builds. The compiler doesn't know where formGroup comes from, so it flags it as invalid HTML.
Here's a component that triggers the error even though the TypeScript itself is correct:
// src/app/app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-root',
template: `
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<input formControlName="email" />
<button type="submit">Login</button>
</form>
`
})
export class AppComponent {
loginForm = new FormGroup({
email: new FormControl(''),
password: new FormControl('')
});
}Angular sees [formGroup] and says: "I don't know what formGroup is — it's not in my directive registry." The component code needs no change at all; the import declaration does.
NgModule apps: add it to the module's imports#
For NgModule-based apps (pre-v14 or hybrid):
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
ReactiveFormsModule // ← add this
],
bootstrap: [AppComponent]
})
export class AppModule { }That single change addresses the cause because ReactiveFormsModule registers the formGroup, formControlName, and formGroupName directives with Angular's DI system — making them available for template compilation.
In practice: open app.module.ts, locate @NgModule({ imports: [...] }), add ReactiveFormsModule to the array, then restart ng serve or run ng build to recompile.
Standalone components (Angular 14+): the import belongs on the component#
Standalone components don't inherit anything from an AppModule — each one declares its own dependencies in the @Component decorator:
// src/app/login/login.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';
import { FormsModule } from '@angular/forms'; // only if you use ngModel
@Component({
selector: 'app-login',
standalone: true,
imports: [
ReactiveFormsModule // ← required for formGroup
// optionally: FormsModule
],
template: `
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<input formControlName="email" />
<button type="submit">Login</button>
</form>
`
})
export class LoginComponent {
loginForm = new FormGroup({
email: new FormControl(''),
password: new FormControl('')
});
onSubmit() {
console.log(this.loginForm.value);
}
}Note: If your standalone component is used in a lazy-loaded module, you must import ReactiveFormsModule directly in the component — importing it in the lazy module alone won't work, because the component's scope is isolated.
The two configurations that still fail after the import#
If the error persists after adding the import, one of these two module-scoping traps is usually in play.
Lazy-loaded child module hosting a standalone component. If LoginComponent lives in LazyModule, and LazyModule imports ReactiveFormsModule, but LoginComponent is standalone and doesn't import ReactiveFormsModule in its @Component decorator, the error persists. Lazy modules don't automatically expose their imports to child standalone components — import ReactiveFormsModule directly in the standalone component.
Shared module without a re-export. If you declare a form component in a shared module and import ReactiveFormsModule there, but forget to re-export it, consumers of the shared module won't get the directives:
// src/app/shared/forms.module.ts
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [ReactiveFormsModule],
exports: [ReactiveFormsModule] // ← critical for shared use
})
export class FormsSharedModule { }Then import FormsSharedModule instead of ReactiveFormsModule directly in other modules.
Confirming the build compiles#
Run:
ng build --configuration developmentYou should see:
✔ Browser application bundle has finished.
✔ Compiling with Angular's incremental compiler (incremental mode).
✔ Build completed.No compiler errors. The form renders, and formGroup binding works — you can type into the inputs and see this.loginForm.value update in the console.
FormsModule vs ReactiveFormsModule — why the split exists#
Reactive forms are opt-in — Angular deliberately separates FormsModule (for template-driven forms with ngModel) from ReactiveFormsModule (for model-driven forms with FormGroup, FormControl, etc.). This keeps the core bundle lean and avoids accidental mixing of patterns.
If you're mixing reactive and template-driven forms (e.g., using ngModel alongside formGroup), remember: FormsModule and ReactiveFormsModule can coexist, but both must be imported. Confusing the two is a common source of errors — see JSX.Element vs ReactNode vs ReactElement: TS2322 Fix for a parallel in React's type system, where missing imports cause similar "unknown property" errors.
To prevent regressions, run ng build in your CI pipeline — if ReactiveFormsModule is missing from the relevant imports array, the build will fail and you'll catch the issue before merge. You can also write a custom angular-eslint rule that flags components or modules using formGroup or formControlName without ReactiveFormsModule declared in their imports array.
I cover similar Angular type-checking pitfalls — like missing imports, incorrect module scope, and template binding errors — in TypeScript Getter Setter Errors: TS1056, TS1028, TS2378 Fix, where I explain how Angular's template compiler enforces strict contracts between directives and modules.
Related#
- Fix TS2564: Property Has No Initializer in TypeScript
- Fix "Property does not exist on Window" in TypeScript
- TypeScript Getter Setter Errors: TS1056, TS1028, TS2378 Fix
- JSX.Element vs ReactNode vs ReactElement: TS2322 Fix
- Fix TS7016: Could Not Find Declaration File for Module
- Fix TS2305: Module Has No Exported Member in TypeScript
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Fix "Property does not exist on Window" in TypeScript
Learn how to safely extend the Window interface in TypeScript using declaration merging, type assertions, and bracket notation to avoid compile-time errors.
JSX.Element vs ReactNode vs ReactElement: TS2322 Fix
Fix TS2322 by understanding when ReactNode, JSX.Element, and ReactElement apply in React + TypeScript component typing.
Fix TS2305: Module Has No Exported Member in TypeScript
TS2305 says the export you're importing doesn't exist under that name. The cause is almost always one of: a typo, default-vs-named confusion, a CJS/ESM interop mismatch, or @types drifting from the runtime package. Each has a precise fix.
Browse by Topic
Find stories that matter to you.
