Home › Frontend › Can't Bind to ngModel: Angular FormsModule Fix
Beginner 5 min · September 23, 2026

Can't Bind to ngModel: Angular FormsModule Fix

Import FormsModule in the owning component or module.

N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Lessons pulled from things that broke in production.

Follow
✓ Production
production tested
September 27, 2026
last updated
2,085
articles · all by Naren
Before you start⏱ 9 min
  • ✓Basic Angular templates and binding
  • ✓Simple TypeScript class properties
  • ✓An Angular CLI project to edit
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • [(ngModel)] is a directive from FormsModule, not native HTML, so Angular rejects the binding until that module is imported
  • Standalone components add FormsModule to their own imports array; NgModule apps add it to the owning module's imports
  • Lazy feature modules need their own FormsModule import because they never inherit the root module's imports
  • Inputs inside a
    tag also need a name attribute or [ngModelOptions]="{standalone: true}" to register correctly
✦ Definition~90s read
What is Angular Can't Bind to ngModel?

Two-way binding with [(ngModel)] syncs an input's displayed value with a component property in both directions: typing updates the property, and code changes update the input. The syntax is nicknamed banana-in-a-box, parentheses inside brackets, and it desugars to a value property binding plus an input-change event binding managed by the NgModel directive.

★
Picture a universal remote with a Netflix button that does nothing until you program it.

That directive, along with helpers like NgForm and NgModelOptions, ships in FormsModule from @angular/forms. Without the module import, the directive doesn't exist in your template's scope, and the compiler reports NG8002: Can't bind to 'ngModel' since it isn't a known property.

Template-driven forms keep their logic in the HTML: names, bindings, and validation attributes describe the form while the component holds plain properties. Angular builds an implicit form model from named inputs, tracking validity and values for submission.

Reactive forms take the opposite path, constructing an explicit FormGroup model in TypeScript and binding inputs with formControlName from ReactiveFormsModule. The two styles solve different problems: template-driven favors simple forms with readable markup, reactive favors dynamic validation and unit-testable models.

Custom components sit outside both styles until bridged. A native input exposes a standard value contract the forms API understands; a bespoke widget doesn't. The ControlValueAccessor interface, writeValue plus change and touch callbacks registered under NG_VALUE_ACCESSOR, translates between ngModel and your component's internals.

Knowing which layer owns your failure, missing module import, missing name, or missing accessor, turns a cryptic compiler error into a three-minute fix.

Plain-English First

Picture a universal remote with a Netflix button that does nothing until you program it. ngModel is that button: the syntax sits on your input, but the behavior lives in FormsModule, which you must import to program it. Standalone components each own their own remote, so every form component needs its own programming step. One import line, and the button starts working everywhere in that template.

You add [(ngModel)]="username" to an input, save, and Angular answers with Can't bind to 'ngModel' since it isn't a known property of 'input'. The syntax looks exactly like the tutorial. The property exists on your class. Yet the compiler insists ngModel is a stranger.

The confusion is natural because ngModel looks like a native HTML attribute, but it isn't one. It's a directive shipped in FormsModule, and Angular only recognizes directives that are imported into the component or module owning your template. No import means no directive, and no directive means the binding matches nothing the compiler knows.

Standalone components made this both simpler and easier to miss. There's no shared AppModule carrying FormsModule for everyone anymore; each standalone component lists its own imports. A form that worked in a module app breaks the moment you convert it without carrying FormsModule along.

This guide fixes it fast and permanently. You'll learn which import each app style needs, why lazy modules need their own copy, what the follow-up name-attribute error means, and when a custom component genuinely needs a ControlValueAccessor bridge versus a plain @Input().

Why ngModel Needs FormsModule to Exist

The [(ngModel)] syntax is shorthand for a property binding plus an event binding, and both halves are delivered by the NgModel directive declared in FormsModule. Native inputs expose value and input events; NgModel wraps them into the two-way contract your template expects. When FormsModule isn't imported, the compiler sees an attribute it can't match to any directive or native property, and NG8002 is its honest report.

Standalone components declare their directive dependencies in the imports array, which replaced the old NgModule declarations-plus-imports dance. Adding FormsModule there scopes the directive to that component's template only. Sibling components don't inherit it, which surprises developers coming from a shared-module world where one import covered dozens of templates. The scoping is deliberate: templates declare exactly what they use, and unused directives never ship.

Verify the fix by checking the error disappears on save without touching the binding itself. If it persists, you edited the wrong owner: the component whose template holds the input is the only imports array that matters. A parent's imports never flow downward. This single rule, owner imports win, resolves the majority of ngModel reports you'll ever see.

src/app/login.component.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [FormsModule],
  template: `
    <label>Username
      <input [(ngModel)]="username" name="username" />
    </label>
    <p>Hello, {{ username }}!</p>
  `,
})
export class LoginComponent {
  username = '';
}
Try it live
📊 Production Insight
When the error survives your edit, you edited the wrong file. Only the component owning the template matters; parent imports never flow down.
🎯 Key Takeaway
NgModel is a FormsModule directive, so only the owning component's imports array can activate the [(ngModel)] syntax in its template.

FormsModule in NgModule vs Standalone Apps

NgModule apps and standalone apps answer the same question, where do directives come from, in different places. In a module app, the declaring module's imports array supplies directives to every template it declares. In a standalone app, each component's imports array supplies directives to its own template. Both are per-owner registrations; neither inherits across boundaries you didn't wire.

Lazy loading draws the sharpest boundary. A lazy feature module gets its own injector and its own compilation scope, so AppModule's FormsModule import is invisible inside it. The symptom is maddening: ngModel works on the home page and fails on the settings page with identical syntax. The fix is a second FormsModule import in the lazy module, which feels redundant until you see modules as sealed scopes rather than a global pool.

Shared modules are the sanctioned way to stop repeating yourself: create a SharedModule that imports and exports FormsModule, then import SharedModule wherever forms appear. Don't over-share, though. Importing every forms API into components that render static text bloats bundles and hides which templates actually bind. Import at the owner, share deliberately, and the error stays away.

src/app/profile.module.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ProfileComponent } from './profile.component';

@NgModule({
  declarations: [ProfileComponent],
  imports: [CommonModule, FormsModule],
  exports: [ProfileComponent],
})
export class ProfileModule {}

// Standalone equivalent: put FormsModule in the
// component's own imports array instead of a module.
// Lazy modules need this import too: they never
// inherit FormsModule from AppModule.
Try it live
📊 Production Insight
Home-page forms passing while lazy-route forms fail is the classic module-boundary tell. Check the lazy module's imports before anything else.
🎯 Key Takeaway
Both eras scope directives per owner, so lazy modules and standalone components each need their own FormsModule import.

The name Attribute Follow-Up Error Inside Forms

Fixing the import often reveals a second error: ngModel inside a form tag needs a name attribute or the standalone option. Template-driven forms build an implicit FormGroup from named inputs, and an unnamed input can't join it. The name attribute is the input's identity in that group; without it, Angular can't track validity or aggregate values on submit.

The standalone option is the escape hatch for inputs that live inside a form tag but shouldn't join the form: search hints, UI-only toggles, draft fields. Marking them standalone keeps their two-way binding working while excluding them from validation and submission values. Use it sparingly. A form where half the inputs are standalone confuses every developer who reads the submit handler, because the model and the form silently disagree.

Expect this error ordering and don't fight it. The compiler reports the unknown property first because without the directive it can't evaluate anything else. Import FormsModule, then add names, then handle custom components. Each fix unlocks the next check. Teams that chase the name error before the import waste time editing templates the compiler hasn't actually validated yet. Patience with the ordering pays off on every future form you'll build.

src/app/search.component.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-search',
  standalone: true,
  imports: [FormsModule],
  template: `
    <form (ngSubmit)="run()">
      <input [(ngModel)]="query" name="query" />
      <input [(ngModel)]="hint"
        [ngModelOptions]="{ standalone: true }" />
      <button type="submit">Go</button>
    </form>
  `,
})
export class SearchComponent {
  query = '';
  hint = '';
  run(): void {
    console.log(this.query, this.hint);
  }
}
Try it live
📊 Production Insight
Fix compiler errors strictly in reported order. The import error masks the name error, and editing for the second before the first clears just burns time.
🎯 Key Takeaway
Named inputs join the implicit form group; standalone-marked inputs bind without joining, and the compiler surfaces this only after the import is fixed.

Custom Components and the ControlValueAccessor Bridge

Custom components hit the same NG8002 error for a different reason: even with FormsModule imported, ngModel has no idea how to read or write your component's value. Native inputs expose a standard value contract; your star-rating widget doesn't. ControlValueAccessor is the bridge interface that teaches ngModel your component's language through writeValue, registerOnChange, and registerOnTouched, plus an NG_VALUE_ACCESSOR registration.

Only build this bridge for genuinely reusable inputs: date pickers, rich selects, rating widgets used across features. For one-off components, plain @Input() and @Output() bindings are simpler, need no forms imports, and keep the data flow visible. Teams burn days writing accessors for components that appear on exactly one page, then maintain that bridge forever for zero reuse benefit.

The most common accessor bug is forgetting to call the registered onChange callback when the internal value changes. The UI updates but ngModel never hears about it, so the parent model goes stale while the widget looks alive. Always call onChange and onTouched at every user interaction point. A quick test that writes a value through ngModel and reads it back catches this before users do.

src/app/stars.component.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { Component, forwardRef } from '@angular/core';
import {
  ControlValueAccessor,
  NG_VALUE_ACCESSOR,
  FormsModule,
} from '@angular/forms';

@Component({
  selector: 'app-stars',
  standalone: true,
  imports: [FormsModule],
  template: `<button (click)="rate(5)">5 stars</button>`,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => StarsComponent),
      multi: true,
    },
  ],
})
export class StarsComponent implements ControlValueAccessor {
  value = 0;
  private onChange: (v: number) => void = () => {};
  private onTouched: () => void = () => {};
  writeValue(v: number): void {
    this.value = v;
  }
  registerOnChange(fn: (v: number) => void): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }
  rate(v: number): void {
    this.value = v;
    this.onChange(v);
    this.onTouched();
  }
}
Try it live
📊 Production Insight
A widget that looks alive while its model stays stale almost always misses an onChange call. Test the round trip, not just the rendering.
🎯 Key Takeaway
Reusable custom inputs join ngModel through ControlValueAccessor plus an NG_VALUE_ACCESSOR provider; one-off components should stay with plain inputs and outputs.

ReactiveFormsModule Won't Fix an ngModel Error

ReactiveFormsModule is the wrong fix for an ngModel error, and reaching for it is the most common misstep after the first import fails. It registers formControlName, formGroup, and the reactive model classes, but not the NgModel directive. The compiler error survives, developers add more imports at random, and the template accumulates modules it never uses.

Each forms style owns distinct syntax. Template-driven uses [(ngModel)] with FormsModule and keeps logic in the HTML. Reactive uses formControlName with ReactiveFormsModule and builds the model in TypeScript. You may import both modules when different inputs use different styles, but never combine both syntaxes on a single input: ngModel with formControlName is deprecated and produces double-registration warnings with values that fight.

Choose per form, not per input. Simple forms with light validation stay template-driven and readable. Dynamic forms with nested groups, conditional validators, or heavy unit testing go reactive, where the FormGroup model is trivially testable without rendering. Write the choice in the component's top comment so the next developer doesn't blend styles out of habit. A visible style decision keeps every future input consistent and every template review short.

src/app/mixed.component.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormControl, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-mixed',
  standalone: true,
  imports: [FormsModule, ReactiveFormsModule],
  template: `
    <input [(ngModel)]="nickname" name="nickname" />
    <form [formGroup]="profile">
      <input formControlName="city" />
    </form>
  `,
})
export class MixedComponent {
  nickname = '';
  profile = new FormGroup({ city: new FormControl('') });
}
Try it live
📊 Production Insight
A template importing both forms modules for a single input is a smell. Split the form by style instead of stacking imports until the error surrenders.
🎯 Key Takeaway
ReactiveFormsModule registers reactive directives only; keep each input on exactly one forms syntax and choose the style per form.

When the Error Survives the Obvious Fix

When the error persists after what looks like the right fix, interrogate three suspects in order. First, ownership: confirm the file you edited actually owns the template holding the binding, since parent imports never flow down and lazy boundaries seal scopes. Second, spelling: ngModel is case-sensitive, and ngmodel or ng-model in lowercase matches nothing. Third, element kind: a custom component needs the accessor bridge, while a native input never does.

A fast diagnostic loop beats guessing. Run grep for FormsModule across the feature to map who imports what, open the owning component's imports array, and compare against the old module's imports if this code was recently converted. Most stubborn cases resolve to editing a parent's imports while the child template starves, or converting to standalone without carrying FormsModule along.

Document the outcome where the next person will look. A one-line comment above the imports array, forms inputs below need FormsModule, saves the next developer the same twenty minutes. Better still, add a spec that renders the real template: mocked-template specs never compile bindings, so they sail past exactly the errors users hit. Real templates in specs turn this runtime surprise into a compile-time pin.

💡The Owner-Imports Rule
Owner imports win: only the component or module holding the template can activate ngModel. Check there first, fix errors in compiler order, and you'll clear nearly every report in minutes.
📊 Production Insight
Stubborn ngModel errors are almost always ownership mistakes: a parent's imports edited while the child's template starves. Grep who owns what before adding more imports.
🎯 Key Takeaway
Check ownership first, spelling second, and element kind third; then pin the fix with a spec that compiles the real template.
● Production incidentPOST-MORTEMseverity: high

The Standalone Conversion That Took Down Login for 52 Minutes

Symptom
Login attempts dropped to zero at 9:41 AM after the conversion deploy. Users saw a blank form area where inputs should have been, and the console showed Can't bind to 'ngModel' since it isn't a known property of 'input' on both fields. The backend login API showed no traffic at all, which misled the first responder into checking the load balancer for twelve minutes.
Assumption
The team assumed converting components to standalone was cosmetic: move the selector, keep the template, delete the module file. The reviewer saw all green checks because the changed pages had unit tests mocking their templates, so no spec ever compiled the real input bindings. Nobody opened the new standalone imports arrays against the old module's imports.
Root cause
The login component was converted from an NgModule declaration to standalone, and the old AuthModule had provided FormsModule to all its templates. The new component's imports array listed only CommonModule and RouterModule, so the [(ngModel)] directives on the username and password inputs matched nothing. The AOT build failed, the deploy pipeline rolled forward a broken bundle, and the login form never rendered a usable input.
Fix
The engineer added FormsModule to the login component's imports array, redeployed, and logins recovered in eleven minutes. The follow-up added a template-compilation smoke test that renders the real login form in CI, plus a migration checklist line requiring every deleted module's imports to be redistributed to the new standalone components.
Key lesson
  • Standalone conversion must redistribute every old module import to the new components; deleting the module file deletes the directives its templates relied on.
  • Mocked component specs can't catch template errors, so at least one spec per form must compile the real template with real imports.
  • Fix compiler errors in reported order: the missing import hides the name-attribute error, and chasing the second error first wastes the whole incident.
Production debug guideFive ngModel failures beginners hit, with the exact import each one needs.5 entries
Symptom · 01
Can't bind to 'ngModel' since it isn't a known property of 'input'
→
Fix
Open the component or module that owns the failing template. For standalone, check the component's imports array; for NgModule apps, check the declaring module's imports. Run grep -rn "FormsModule" src/ to see where it is today. If it's absent at the owner level, add it there, restart ng serve, and confirm the error line is gone before touching anything else.
Symptom · 02
ngModel works on root pages but fails on a lazy-loaded route
→
Fix
Find which route owns the failing template and open its feature module or component. Lazy modules never inherit root imports, so confirm FormsModule appears in the lazy module's own imports or the standalone route component's imports. Add it at that level and lazy-load the route again. If the root form works but the lazy one failed, this boundary was the cause.
Symptom · 03
A second error about the name attribute appears right after the import fix
→
Fix
After the import fix, read the next compiler error fully. If it demands a name attribute or standalone option, add name="email" to the input inside the form tag, or [ngModelOptions]="{standalone: true}" for inputs that shouldn't join the form. Save and confirm both errors clear together.
Symptom · 04
Error names a custom component property, and FormsModule is already imported
→
Fix
Check whether the failing element is your own component selector rather than a native input. If it is, decide: plain @Input/@Output stays simple with no forms imports, while true [(ngModel)] support needs ControlValueAccessor plus an NG_VALUE_ACCESSOR provider. Implement the bridge only for reusable inputs, then re-run the template check.
Symptom · 05
Error persists even though a forms module was imported
→
Fix
Open the imports and look for ReactiveFormsModule sitting where FormsModule should be. Reactive bindings don't register ngModel, so swap or supplement: FormsModule for [(ngModel)], ReactiveFormsModule for formControlName. If the template mixes both styles on different inputs, import both modules and keep each input on exactly one syntax.
ngModel Binding Failures Compared
Root CauseHow to ConfirmFixPrevention
FormsModule never importedError names ngModel as unknown property and grep finds no FormsModule in the owning component or moduleStandalone: add FormsModule to the component imports; NgModule: add it to the module importsScaffold form components with FormsModule pre-imported; fail CI on NG8002
FormsModule imported at the wrong levelWorks in root templates but fails in a lazy feature or a sibling componentImport FormsModule in the exact module or standalone component that owns the templateKeep a per-module imports checklist in PR reviews for feature modules
ngModel used without name inside a formSecond error after the import fix demands a name attribute or standalone optionAdd name="field" to the input or [ngModelOptions]="{standalone: true}"Always include name attributes on template-driven inputs by habit
Custom component without ControlValueAccessorError names your custom selector's property and FormsModule is already importedImplement ControlValueAccessor with NG_VALUE_ACCESSOR, or bind with @Input/@Output insteadDecide at design time whether a custom input joins the forms API or stays a plain component
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
srcapplogin.component.ts@Component({Why ngModel Needs FormsModule to Exist
srcappprofile.module.ts@NgModule({FormsModule in NgModule vs Standalone Apps
srcappsearch.component.ts@Component({The name Attribute Follow-Up Error Inside Forms
srcappstars.component.tsControlValueAccessor,Custom Components and the ControlValueAccessor Bridge
srcappmixed.component.ts@Component({ReactiveFormsModule Won't Fix an ngModel Error

Key takeaways

1
[(ngModel)] is a FormsModule directive, not native HTML, so the owning component or module must import FormsModule.
2
Standalone components import FormsModule in their own imports array; NgModule apps import it per module.
3
Lazy feature modules need their own FormsModule import because they never inherit root imports.
4
Inputs inside a form tag also need a name attribute or the standalone ngModel option.
5
Custom components support ngModel only through a ControlValueAccessor bridge with NG_VALUE_ACCESSOR.
6
ReactiveFormsModule does not provide ngModel; importing it by mistake leaves the error in place.

Common mistakes to avoid

5 patterns
×

Importing FormsModule in AppModule but using ngModel in a lazy feature

Symptom
Root templates bind fine but a lazy route's input throws the ngModel error, because lazy modules don't inherit AppModule's imports.
Fix
Import FormsModule in the exact component or module that owns the template: the standalone component's imports array, or the feature module's imports. Re-run ng serve and confirm the red binding underline is gone.
×

Using ngModel inside a form tag without a name attribute

Symptom
Fixing the known-property error reveals a second error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined with standalone option.
Fix
Add name="email" or [ngModelOptions]="{standalone: true}" to the input. The name attribute is the right default inside real forms.
×

Mixing [(ngModel)] with formControlName on the same input

Symptom
The known-property error is gone but the console warns that ngModel with formControlName is deprecated, and values update twice or fight each other.
Fix
Pick one syntax per input. Template-driven forms use [(ngModel)]; reactive forms use formControlName with ReactiveFormsModule. Mixing them double-registers the control.
×

Implementing ControlValueAccessor to fix a plain input

Symptom
Hours lost writing a value-accessor bridge for a native input that only ever needed FormsModule imported.
Fix
Stop at the FormsModule import for built-ins. Only implement ControlValueAccessor when you're authoring a reusable custom input component, and register it with NG_VALUE_ACCESSOR.
×

Importing ReactiveFormsModule and expecting ngModel to work

Symptom
The error persists after adding an import, because ReactiveFormsModule registers reactive directives, not the ngModel template-driven directive.
Fix
Add FormsModule for [(ngModel)]; add ReactiveFormsModule only for formControlName and FormGroup. Import both only when the template genuinely mixes both styles.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Why does [(ngModel)] need FormsModule imported?
Q02JUNIOR
Where do you import FormsModule in module vs standalone apps?
Q03SENIOR
How do you make [(ngModel)] work on a custom component?
Q04SENIOR
Compare template-driven and reactive forms.
Q05SENIOR
Walk through debugging NG8002 on ngModel in a large app.
Q01 of 05JUNIOR

Why does [(ngModel)] need FormsModule imported?

ANSWER
Because [(ngModel)] is a directive declared in FormsModule, not a native HTML attribute. Angular only knows directives that are imported into the component or module owning the template. Without the import, the compiler treats ngModel as an unknown property, which is exactly what NG8002 reports.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Does ReactiveFormsModule include ngModel?
02
When should I pick ngModel over reactive forms?
03
Can I use ngModel inside a form tag?
04
Why does the name error appear only after I fix the import?
05
Do all custom inputs need ControlValueAccessor?
06
Does this error appear in production builds too?
N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Lessons pulled from things that broke in production.

Follow
✓ Verified
production tested
September 27, 2026
last updated
2,085
articles · all by Naren
🔥

That's Angular. Mark it forged?

5 min read · try the examples if you haven't

←
Previous
Angular NullInjectorError: No Provider for Service
2 / 6 · Angular
Next
Angular ExpressionChangedAfterItHasBeenCheckedError
→