Fellow developers,

We all know that scenario where nothing seems broken — no errors, no warnings — yet the interface just... refuses to behave as expected.

That is exactly how this saga kicked off.

However, let me give you a bit of context beforehand...


🧳 Coming Back to Angular After Half a Decade

Only recently did I return to Angular, following nearly five years of absence.

Everything I once knew instinctively?

Either vanished or transformed.

Now we have standalone: true. Modules are no longer mandatory. Components act more like self-contained pieces.

It’s still Angular — yet somehow not.

Thus, when I set out to create a simple standalone navbar component featuring a dropdown filter, I believed I was ready to jump straight in.

After all, what could possibly be tough about *ngFor?

As it turned out, quite a bit — especially when you're working without full awareness.


🚧 The Configuration

My task involved building a home-navbar component — a straightforward filter offering options like “Latest”, “Trending”, and “Following”.

Within home-navbar.component.ts, this is what I assembled:

filterOptions = ['latest', 'trending', 'following'];
Enter fullscreen mode Exit fullscreen mode

And in the template:

<ul>
  <li *ngFor="let option of filterOptions">{{ option }}</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

What a vision of simplicity. What a sight to behold.

And yet... it completely bypasses all logic.


😵‍💫 Chasing an Invisible Glitch

Zero errors raised. Zero warnings emitted. All you get is a bare <ul>.

It’s the kind of failure where Angular stays silent — brushing it off without a hint.

My debugging steps went like this:

  • ✅ I called console.log(filterOptions) — the data was populated.

  • ✅ I wrote {{ filterOptions }} inside the template — it output perfectly.

  • *ngFor — utterly ignored, as if it had never been added.

Then I even swapped it out for:

<li *ngFor="let item of ['a', 'b', 'c']">{{ item }}</li>
Enter fullscreen mode Exit fullscreen mode

Still nothing.

Then I tried this:

<div *ngIf="true">Hello ngIf</div>
Enter fullscreen mode Exit fullscreen mode

And when that failed to appear on screen...

I completely lost it.


💡 The Realization

After a moment of panic, I turned to the documentation. And then it clicked:

Since this is a standalone component, it gets no inherited imports—not even Angular’s core directives.

That’s when I finally saw what I had been overlooking the whole time:

import { CommonModule } from '@angular/common';

@Component({
  standalone: true,
  ...
  imports: [CommonModule, ...] // ← The missing piece
})
Enter fullscreen mode Exit fullscreen mode

That single line was the culprit behind every silent failure in the codebase.


🔍 Why It Happens

Here's how Angular handles this:

  • For regular components within an NgModule, a single CommonModule import provides directives to every declared component.

  • However, standalone components are entirely on their own.

  • In that case, you have to add CommonModule to your imports manually — otherwise *ngIf, *ngFor, | date, | currency, and similar features just won’t be available.


🤦 Lesson from the Drama

My attention was so locked on crafting the UI and getting features to work that I never paused to consider a simpler question:

"Hold on, what actually supplies structural directives?"

That moment served as a powerful wake-up call:

Even for those with years of experience, it's all too easy to slip into routine and miss the details that carry real weight.

Honestly, I wasn't really practicing Angular — I was going through the motions.

And Angular itself? It has moved on.


✅ The Fix

Adding this single import brought everything back into order:

imports: [CommonModule]
Enter fullscreen mode Exit fullscreen mode

After that addition, *ngFor started functioning again. *ngIf also resumed working. Everything was back to normal.


✨ Update: Angular 17+ Alternative Without CommonModule

As of Angular 17, a more streamlined approach exists for managing loops that requires no CommonModule import whatsoever.

✅ The templates now support the native @for and @if control flow directly.

Here's an example:

<ul>
  @for (option of filterOptions; track $index) {
    <li>{{ option }}</li>
  }
</ul>
Enter fullscreen mode Exit fullscreen mode

or conditionally:

@if (filterOptions.length > 0) {
  <p>We have {{ filterOptions.length }} options!</p>
}
Enter fullscreen mode Exit fullscreen mode

🏆 Best Practice

  • Angular 16 or earlier:

    Go with CommonModule.

  • Angular 17 or later:

    Opt for the @for and @if syntax.

  • When older Angular versions are a requirement, keep using *ngFor and *ngIf along with CommonModule.

🔑 Takeaways

  • Standalone components carry standalone duties.

    No dependency is brought in automatically — you decide precisely what gets imported.

  • Missing CommonModule?

    Then zero *ngFor, zero *ngIf, zero pipes — and not a single error message.

    Angular passes over them in silence, which leaves you puzzled.

  • Re-entering the framework after a long pause?

    Don't expect the old rules to hold.
    Even well-known features shift — usually in subtle, quiet increments.

  • Minor details lead to major, quiet failures.

    Pause and double-check the fundamentals every time — that's where issues usually appear first.

  • Extra perk for Angular 17+:

    The updated @for and @if syntax trims boilerplate further — no CommonModule required whatsoever!

🗯️ Have You Had “Silent Failures” Too?

Have you burned hours hunting down a bug that turned out to be just one absent import?

Or watched a component misbehave because of an unspoken Angular constraint?

Tell us about it.

Let’s embrace the awkward stage of learning and relearning.

After all, it’s not just about mastering Angular.

It’s about knowing which details deserve a second look.