Component Anatomy — @Component, Template, Styles and Metadata

An Angular component is the fundamental building block of the UI. Every visible element — from a button to an entire page — is a component. The @Component decorator configures the component, the class holds its logic and state, and the template describes its UI structure. Understanding every part of the component anatomy is the foundation for all Angular development. Angular 18’s standalone components are simpler than NgModule-based components — no separate module declaration needed, all dependencies declared directly on the component.

Complete Component Anatomy

import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
import { CommonModule, DatePipe }                      from '@angular/common';
import { RouterLink }                                  from '@angular/router';
import { PostCardComponent }                           from '@shared/components/post-card.component';
import { PostSummaryDto }                              from '@models/post';

@Component({
  // ── Selector — how this component is used in other templates ─────────────
  selector: 'app-post-list',   // used as: <app-post-list />
  // selector: '[appHighlight]'  // attribute selector: <div appHighlight>
  // selector: '.my-class'       // class selector (rare)

  // ── Standalone — no NgModule needed ───────────────────────────────────────
  standalone: true,

  // ── Imports — dependencies this template uses ─────────────────────────────
  imports: [
    CommonModule,        // ngClass, ngStyle (legacy directives)
    DatePipe,            // | date pipe
    RouterLink,          // routerLink directive
    PostCardComponent,   // child component used in template
  ],

  // ── Template — inline or external ────────────────────────────────────────
  template: `
    <section class="post-list">
      <h1>{{ title }}</h1>

      <!-- Property binding: sets DOM property from component property ──── -->
      <img [src]="heroImageUrl" [alt]="title">

      <!-- Event binding: calls method on DOM event ───────────────────────── -->
      <button (click)="onRefresh()">Refresh</button>

      <!-- Two-way binding: combines [] and () ────────────────────────────── -->
      <input [(ngModel)]="searchQuery" placeholder="Search posts...">

      <!-- New @for control flow (Angular 17+) ──────────────────────────── -->
      @for (post of posts(); track post.id) {
        <app-post-card [post]="post" />
      } @empty {
        <p class="empty-state">No posts available.</p>
      }

      <!-- Pipe in template ───────────────────────────────────────────────── -->
      <p>Last updated: {{ lastUpdated | date:'medium' }}</p>

      <!-- Conditional ────────────────────────────────────────────────────── -->
      @if (isLoading()) {
        <div class="spinner">Loading...</div>
      }
    </section>
  `,

  // ── Styles — scoped to this component (ViewEncapsulation.Emulated) ────────
  styles: [`
    .post-list { max-width: 800px; margin: 0 auto; }
    .empty-state { color: #888; text-align: center; padding: 2rem; }
  `],

  // ── Change detection strategy ─────────────────────────────────────────────
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PostListComponent {
  // Class properties become available in the template
  title        = 'Latest Posts';
  heroImageUrl = '/assets/hero.jpg';
  searchQuery  = '';
  lastUpdated  = new Date();

  // Signals for reactive state
  posts     = signal<PostSummaryDto[]>([]);
  isLoading = signal(false);

  onRefresh(): void {
    // Called by (click) event binding
    this.isLoading.set(true);
    // ... load posts
  }
}
Note: ViewEncapsulation controls how component styles are scoped. The default Emulated mode adds unique attribute selectors to the component’s styles (e.g., .post-list[_ngcontent-xyz]) so they only affect elements in this component’s template. None makes styles global — they can affect other components. ShadowDom uses the browser’s native Shadow DOM for true style isolation. For most components, Emulated (the default) is correct — it provides style scoping without requiring Shadow DOM browser support.
Tip: Use external template files (templateUrl: './post-list.component.html') for components with complex templates and inline templates for simple, short templates. External files benefit from full HTML editor support (syntax highlighting, linting, auto-complete). Inline templates are convenient for small components where the template fits on one screen. The Angular team recommends external files for anything beyond ~10 lines of template HTML. The same applies to styles — external styleUrls vs inline styles.
Warning: Property binding ([src]="imageUrl") is different from attribute binding ([attr.colspan]="colspanValue"). Property binding sets the DOM property (the JavaScript object property on the element), which is usually what you want. Attribute binding sets the HTML attribute, needed for attributes that do not have corresponding DOM properties (like colspan, aria-*, and data-*). A common mistake is using [href] instead of routerLink for navigation — [href] causes a full page reload; routerLink uses Angular’s router for client-side navigation.

Template Binding Quick Reference

Syntax Type Direction Example
{{ value }} Interpolation Component → DOM {{ post.title }}
[property]="expr" Property binding Component → DOM [src]="imageUrl"
(event)="method()" Event binding DOM → Component (click)="save()"
[(ngModel)]="prop" Two-way binding Both [(ngModel)]="title"
[attr.x]="val" Attribute binding Component → DOM [attr.aria-label]="label"
#ref Template ref DOM element ref #searchInput

Common Mistakes

Mistake 1 — Using [href] for internal navigation (full page reload)

❌ Wrong — <a [href]="'/posts/' + post.slug"> causes a full page reload; loses Angular state.

✅ Correct — <a [routerLink]="['/posts', post.slug]"> uses Angular router; stays in the SPA.

Mistake 2 — Forgetting to import used directives/components in standalone components

❌ Wrong — using <app-post-card> in template without importing PostCardComponent; NG8001 error.

✅ Correct — add every component, directive, and pipe used in the template to the component’s imports: [] array.

🧠 Test Yourself

A template has <img [src]="post.imageUrl">. When post.imageUrl is null, what renders in the browser?