Image description

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 ensure FormsModule is imported.
  • When using structural or attribute directives like *ngIf, *ngFor, ngClass, ngStyle, etc., import CommonModule.
  • 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.

angular #typescript #signals #frontend #architecture