Components and Templates

Standalone components, interpolation, property/event binding, and the @if/@for control flow.

A standalone component

An Angular component is a TypeScript class decorated with @Component, pairing a template with the logic that drives it. Standalone components (standalone: true) are the modern default — they declare their own dependencies directly, with no separate NgModule required to wire them up:

Typescript
import { Component } from "@angular/core";

@Component({
  selector: "app-greeting",
  standalone: true,
  template: `<h2>Hello, {{ name }}!</h2>`,
})
export class GreetingComponent {
  name = "NOA Labs";
}

selector: "app-greeting" is the custom HTML tag this component renders as (<app-greeting></app-greeting>) when used elsewhere. standalone: true means this component can be imported and used directly by any other standalone component without first registering it in an NgModule — as of current Angular versions, standalone: true is even the implicit default, but writing it explicitly still makes the intent clear, especially in a codebase that also has older, non-standalone components.

For anything beyond a one-line template, a separate template file is more common:

Typescript
import { Component } from "@angular/core";

@Component({
  selector: "app-greeting",
  standalone: true,
  templateUrl: "./greeting.component.html",
  styleUrl: "./greeting.component.css",
})
export class GreetingComponent {
  name = "NOA Labs";
}
HTML
<!-- greeting.component.html -->
<h2>Hello, {{ name }}!</h2>

Interpolation and property/event binding

Angular's template syntax covers three core bindings, each with a distinct purpose:

HTML
<!-- Interpolation: embed a component property's value as text -->
<h2>Hello, {{ name }}!</h2>

<!-- Property binding: bind an element/component property to an expression -->
<img [src]="imageUrl" [alt]="imageAlt" />
<button [disabled]="isSaving">Save</button>

<!-- Event binding: run a method when an event fires -->
<button (click)="increment()">+1</button>
Typescript
import { Component } from "@angular/core";

@Component({
  selector: "app-counter",
  standalone: true,
  template: `
    <p>Count: {{ count }}</p>
    <button (click)="increment()">+1</button>
    <button (click)="decrement()" [disabled]="count === 0">-1</button>
  `,
})
export class CounterComponent {
  count = 0;

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }
}

[property]="expression" (square brackets) binds a value into the element; (event)="handler()" (parentheses) binds a handler to fire on an event. This bracket/parenthesis distinction is deliberate and consistent throughout every Angular template — square brackets always mean "data going in," parentheses always mean "an event coming out."

Control flow: @if / @for vs *ngIf / *ngFor

Angular templates need dedicated syntax for conditionals and loops, since plain JavaScript if/for statements can't be embedded directly in HTML. Two generations of syntax exist for this.

The modern built-in control flow (@if, @for, @switch) was introduced as Angular's new default and requires no import:

HTML
@if (isLoggedIn) {
  <p>Welcome back!</p>
} @else {
  <p>Please log in.</p>
}

<ul>
  @for (fruit of fruits; track fruit) {
    <li>{{ fruit }}</li>
  } @empty {
    <li>No fruits yet.</li>
  }
</ul>

The older structural directives (*ngIf, *ngFor) are still extremely common in existing codebases and documentation, and require importing CommonModule (or the specific directive) into the component:

Typescript
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";

@Component({
  selector: "app-fruit-list",
  standalone: true,
  imports: [CommonModule],
  template: `
    <p *ngIf="isLoggedIn; else loggedOut">Welcome back!</p>
    <ng-template #loggedOut><p>Please log in.</p></ng-template>

    <ul>
      <li *ngFor="let fruit of fruits">{{ fruit }}</li>
    </ul>
  `,
})
export class FruitListComponent {
  isLoggedIn = false;
  fruits = ["Apple", "Banana", "Cherry"];
}

The @if/@for block syntax is the recommended choice for new code — it's built into the compiler directly (no import needed), and generally performs better, particularly with @for's mandatory track expression (Angular's equivalent of React's/Vue's list key, telling Angular how to identify each item across re-renders). You'll still need to recognize *ngIf/*ngFor when reading existing Angular code, since a large share of production codebases predate the newer syntax.

Common mistakes

  • Confusing [property] binding (data going into the element) with (event) binding (an event coming out) — mixing up the brackets and parentheses is a very common beginner typo.
  • Forgetting track in a @for block (or a trackBy function with *ngFor) — without a stable tracking expression, Angular can't efficiently identify which list items actually changed, hurting both performance and correctness when reordering.
  • Using *ngIf/*ngFor in a standalone component without importing CommonModule (or the specific directive) — unlike the built-in @if/@for, the older structural directives are not available by default.