Interpolation and Property Binding — Displaying Data in Templates

📋 Table of Contents
  1. All Binding Forms
  2. Common Mistakes

Data binding in Angular templates connects component state to the DOM. Interpolation ({{ }}) renders text; property binding ([property]) sets DOM properties; attribute, class, and style bindings handle the full range of DOM manipulation needs. Understanding which binding type to use for each scenario — and why property binding is different from attribute binding — prevents a common category of Angular bugs where the right DOM property is not being set.

All Binding Forms

@Component({
  selector:   'app-post-card',
  standalone:  true,
  imports:    [NgClass, NgStyle],   // for [ngClass] and [ngStyle]
  template: `
    <!-- ── Interpolation — inserts text ────────────────────────────────── -->
    <h2>{{ post.title }}</h2>
    <p>By {{ post.authorName }} · {{ post.viewCount }} views</p>

    <!-- ── Property binding — sets DOM properties ──────────────────────── -->
    <img [src]="post.coverImageUrl ?? '/assets/default.jpg'"
         [alt]="post.title">

    <button [disabled]="!canEdit">Edit</button>

    <!-- ── Attribute binding — for HTML attrs without DOM property ──────── -->
    <td [attr.colspan]="colSpan">...</td>
    <button [attr.aria-label]="'Delete post: ' + post.title">🗑</button>
    <div [attr.data-post-id]="post.id"></div>

    <!-- ── Class binding — toggle a single CSS class ───────────────────── -->
    <article
      [class.featured]="post.isFeatured"
      [class.published]="post.isPublished"
      [class.draft]="!post.isPublished"
    >

    <!-- ── [ngClass] — multiple classes at once ────────────────────────── -->
    <div [ngClass]="{
      'card':       true,
      'card-large': post.viewCount > 1000,
      'card-new':   isNewPost(post),
      'card-mine':  post.authorId === currentUserId
    }"></div>

    <!-- ── Style binding — inline style from component value ───────────── -->
    <div [style.backgroundColor]="post.isPublished ? '#e8f5e9' : '#fff3e0'">
    <div [style.font-size.px]="fontSize"></div>  <!-- px unit shorthand ──── -->

    <!-- ── [ngStyle] — multiple styles at once ──────────────────────────── -->
    <div [ngStyle]="{
      'opacity': post.isPublished ? 1 : 0.6,
      'border-left': '4px solid ' + categoryColor
    }"></div>

    <!-- ── Safe navigation operator — prevents null errors ─────────────── -->
    <p>{{ post.author?.displayName }}</p>
    <img [src]="post.author?.avatarUrl">
  `,
})
export class PostCardComponent {
  @Input({ required: true }) post!: PostSummaryDto;
  @Input() canEdit     = false;
  @Input() currentUserId = '';

  colSpan      = 3;
  fontSize     = 16;
  categoryColor = '#2196f3';

  isNewPost(post: PostSummaryDto): boolean {
    const daysSincePublished = (Date.now() - new Date(post.publishedAt).getTime())
      / (1000 * 60 * 60 * 24);
    return daysSincePublished < 7;
  }
}
Note: Property binding sets DOM properties (JavaScript object properties on element nodes). Attribute binding sets HTML attributes (the string-valued attributes in the HTML markup). Most HTML attributes have corresponding DOM properties and the two stay in sync — but some do not. colspan is a HTML attribute with no matching property called “colspan” — you must use [attr.colspan]. Similarly, aria-* and data-* attributes have no direct DOM property equivalents. When in doubt: if you can set it with element.propertyName = value in JavaScript, use [propertyName]; otherwise use [attr.attributeName].
Tip: Use the safe navigation operator (?.) in template expressions to prevent “Cannot read properties of null” errors when displaying nested optional values. {{ post.author?.displayName }} renders nothing (blank) if post.author is null or undefined, rather than throwing an error. Combine with the nullish coalescing operator for a fallback value: {{ post.author?.displayName ?? 'Unknown Author' }}. This is especially useful when API responses have nullable nested objects.
Warning: Never use string interpolation to set HTML that will be rendered as markup — [innerHTML]="'<b>' + title + '</b>'" risks XSS if title contains user-provided HTML. Angular sanitises bound HTML values but the safest approach is to never bind user content as HTML. Use interpolation ({{ title }}) for text — Angular escapes HTML entities automatically. If you genuinely need to render HTML from a trusted source (server-generated HTML), use Angular’s DomSanitizer.bypassSecurityTrustHtml() explicitly, acknowledging the security responsibility.

Common Mistakes

Mistake 1 — Using property binding for attributes without DOM properties

❌ Wrong — [colspan]="3"; no DOM property named “colspan”; binding has no effect.

✅ Correct — [attr.colspan]="3"; sets the HTML attribute correctly.

Mistake 2 — Forgetting safe navigation on nullable nested properties

❌ Wrong — {{ post.author.displayName }}; throws if author is null before data loads.

✅ Correct — {{ post.author?.displayName }}; renders blank while data is loading.

🧠 Test Yourself

A template has [disabled]="isSubmitting" on a button, where isSubmitting is a boolean. When isSubmitting is false, what attribute state does the button have?