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:
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:
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";
}
<!-- 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:
<!-- 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>
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:
@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:
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
trackin a@forblock (or atrackByfunction 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/*ngForin a standalone component without importingCommonModule(or the specific directive) — unlike the built-in@if/@for, the older structural directives are not available by default.