If you're building a modern Angular 19 app using standalone components, chances are you've encountered this error:
Can't bind to 'ngModel' since it isn't a known property of 'input'
Can't bind to 'ngClass' since it isn't a known property of 'li'
These errors are common—and completely expected—when the required Angular modules are not imported explicitly in standalone components. Let’s explore why this happens and how to resolve it like a pro.
The Problem
In Angular 19, many developers are adopting standalone components to reduce boilerplate and improve modularity. However, unlike traditional NgModules, standalone components must declare their dependencies explicitly via the imports
array.
So, if you use template features like:
-
[(ngModel)]
for two-way binding -
[ngClass]
for conditional class styling
You’ll need to import the respective Angular modules: FormsModule
and CommonModule
.
The Solution
Update your standalone component definition to include required modules in the imports
property of the @Component
decorator.
Example Fix: dragonball.component.ts
<div class="dbz-container">
<h1 class="dbz-title">⚡ Dragon Ball Power Level Tracker ⚡</h1>
<form class="dbz-form" (ngSubmit)="addCharacter()">
<input
type="text"
placeholder="Character Name"
[(ngModel)]="newCharacter.name"
name="name"
required
/>
<input
type="number"
placeholder="Power Level"
[(ngModel)]="newCharacter.powerLevel"
name="powerLevel"
required
/>
<button type="submit">Add Fighter</button>
</form>
<ul class="dbz-list">
<li *ngFor="let character of characters" [ngClass]="getPowerClass(character.powerLevel)">
<span class="name">{{ character.name }}</span>
<span class="power">PL: {{ character.powerLevel }}</span>
<button class="remove" (click)="removeCharacter(character)">✖</button>
</li>
</ul>
</div>
<style>
.dbz-container {
max-width: 600px;
margin: auto;
padding: 2rem;
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #ff9a9e, #fad0c4);
border-radius: 1rem;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}
.dbz-title {
text-align: center;
color: #fff;
font-size: 2rem;
text-shadow: 2px 2px 4px #000;
}
.dbz-form {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 1rem;
}
.dbz-form input {
padding: 0.5rem;
border-radius: 0.5rem;
border: 1px solid #ccc;
}
.dbz-form button {
background-color: #ff5722;
color: #fff;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.5rem;
cursor: pointer;
transition: 0.3s;
}
.dbz-form button:hover {
background-color: #e64a19;
}
.dbz-list {
list-style: none;
padding: 0;
}
.dbz-list li {
background-color: #fff3e0;
padding: 1rem;
margin: 0.5rem 0;
border-radius: 0.5rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.dbz-list li.super-saiyan {
border-left: 6px solid gold;
background: linear-gradient(to right, #fff3e0, #ffeb3b);
}
.dbz-list li.strong {
border-left: 6px solid orange;
}
.dbz-list li.normal {
border-left: 6px solid gray;
}
.name {
font-weight: bold;
color: #333;
}
.power {
font-size: 0.9rem;
color: #666;
}
.remove {
background: transparent;
border: none;
color: red;
font-size: 1.2rem;
cursor: pointer;
}
</style>
<script lang="ts">
import { Component } from '@angular/core';
@Component({
selector: 'app-dragonball',
standalone: true,
imports: [CommonModule, FormsModule], // 👈 Include necessary Angular modules
templateUrl: './dragonball.component.html',
styleUrls: ['./dragonball.component.scss'],
})
export class DragonBallComponent {
newCharacter = { name: '', powerLevel: 0 };
characters: { name: string; powerLevel: number }[] = [];
addCharacter() {
if (this.newCharacter.name && this.newCharacter.powerLevel > 0) {
this.characters.push({ ...this.newCharacter });
this.newCharacter = { name: '', powerLevel: 0 };
}
}
removeCharacter(character: { name: string; powerLevel: number }) {
this.characters = this.characters.filter(c => c !== character);
}
getPowerClass(powerLevel: number): string {
if (powerLevel >= 9000) return 'super-saiyan';
if (powerLevel >= 3000) return 'strong';
return 'normal';
}
}
</script>
Why This Happens
In Angular’s standalone ecosystem, components are self-contained and don’t inherit module-level configuration like before. That means:
- No implicit access to directives like
NgModel
,NgClass
,NgIf
, etc. - You must opt-in to each dependency your component needs.
This behavior is by design. It promotes explicitness and better tree-shaking during builds.
Best Practices
- When using
[(ngModel)]
, always ensureFormsModule
is imported. - When using structural or attribute directives like
*ngIf
,*ngFor
,ngClass
,ngStyle
, etc., importCommonModule
. - Keep components lean and self-sufficient by only importing what is required.
Need Full Setup?
Make sure you're also bootstrapping your application using standalone APIs (introduced in Angular 14+):
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [],
});
Conclusion
Modern Angular offers powerful flexibility through standalone components—but with that comes the responsibility of managing dependencies yourself.
Next time you see those cryptic ngModel
or ngClass
errors, remember: they're not bugs—they're reminders to be explicit. 💡
Happy coding with Angular 19! And remember: with great standalone power comes great module responsibility.