Mert Özen Further With Every Line
Frontend Angular — #05

Data Binding in Angular: Interpolation, Property, Event and Two-Way Binding

Mert Özen Oct 31, 2026 6 min 2 views
Data Binding in Angular: Interpolation, Property, Event and Two-Way Binding

Every bridge between class and template in Angular 12: interpolation, property, attribute, class and style binding, event binding and $event, template reference variables and [(ngModel)].

Version note: The code and commands in this post are for Angular 12.

The Bridge Between Class and Template

A component has two halves: the class that holds the data and the template that shows it. Data binding is the name of the bridge that connects them. Traffic on that bridge flows both ways. Data going from the class to the template shows up on the screen, and events going from the template to the class tell your code what the user just did.

Each direction has its own syntax in Angular, and once that clicks, templates become much easier to read. Curly braces and square brackets go from class to template, round brackets go from template to class, and both together go both ways. In this post we will see all of them through one small example: a user profile card.

Run ng g c profile-card with the CLI and fill in the class like this:

export class ProfileCardComponent {
  name = 'Elif Yılmaz';
  role = 'Frontend Developer';
  city = 'İzmir';
  avatarUrl = 'assets/elif.png';
  isOnline = true;
  isSaving = false;
  followers = 128;
  progress = 70;

  follow(): void {
    this.followers++;
  }
}

Interpolation: Curly Braces

Let's start with interpolation, which we saw earlier:

<h3>{{ name }}</h3>
<p>{{ role }} · {{ followers }} takipçi</p>
<p>Durum: {{ isOnline ? 'Çevrim içi' : 'Çevrim dışı' }}</p>
<p>{{ name.toUpperCase() }}</p>

What goes inside the curly braces doesn't have to be just a property name; you can write a small TypeScript expression. On the third line we picked a label with the ternary operator, and on the fourth we called a string method. Angular turns the result into text and prints it.

It has limits, though. Assignments (=), ++ or new don't work inside interpolation. The rule is: a template expression reads a value, it doesn't change anything. If the logic starts to grow, move it into the class as a method or a property.

Property Binding: Square Brackets

<img [src]="avatarUrl" [alt]="name">
<button [disabled]="isSaving">Kaydet</button>

Square brackets bind a property of an HTML element to a value in the class. [src]="avatarUrl" means "the img element's src property should always be the value of avatarUrl." When avatarUrl changes, the image changes too.

What the brackets really say is that the content of the quotes is a TypeScript expression. Write alt="name" and the alt text is literally the word "name"; write [alt]="name" and the property's value is read. That's why, if you want to pass a fixed string with square brackets, you need extra single quotes inside: [alt]="'Profile photo'".

You may have noticed that src="{{ avatarUrl }}" does the same job. Angular turns that form into a property binding behind the scenes. For string values either works, but when you bind a boolean like disabled, square brackets are a must; interpolation turns everything into text.

Attribute, Class and Style Binding

Property binding has a subtlety: HTML attributes and DOM properties are not the same thing. An attribute is what you write in the HTML; a property is a field on the JavaScript object the browser creates from that element. Most of the time they match by name, but sometimes they don't. The colspan of a table cell is the classic example:

error NG8002: Can't bind to 'colspan' since it isn't a known property of 'td'.

In cases like this, or for attributes like aria-label that have no matching property, you bind the attribute directly:

<td [attr.colspan]="2">Toplam</td>
<button [attr.aria-label]="'Takip et: ' + name">+</button>

The same dot notation works for CSS classes and styles:

<span class="status" [class.online]="isOnline">●</span>
<div class="bar" [style.width.%]="progress"></div>

[class.online] adds the online class to the element when isOnline is true and removes it when it's false. It leaves the existing status class alone. [style.width.%] sets the width as a percentage; the unit at the end can be px, em or %. We will cover more convenient ways to manage several classes at once in upcoming posts.

Event Binding: Round Brackets

Now let's reverse the traffic. To tell the class when the user does something:

<button (click)="follow()">Takip et</button>

Inside the brackets is the name of the event to listen to; inside the quotes is the code that runs when it happens. Every click runs follow(), followers goes up by one, and thanks to interpolation the number on the screen updates by itself. This loop is Angular's core promise: you change the data, the screen takes care of the rest.

Unlike interpolation, you can assign here. (click)="isOnline = !isOnline" is a valid event statement. Still, putting anything longer than one line into a method in the class keeps the template readable.

$event and Strict Mode

Sometimes you need the event itself: which key was pressed, what was typed into the input? Angular gives you the event object in the template as $event. Let's try a line you will see a lot online:

<input (input)="name = $event.target.value">

In Angular 12's strict mode this line doesn't compile:

error TS2339: Property 'value' does not exist on type 'EventTarget'.
error TS2531: Object is possibly 'null'.

The compiler has a point. $event is a generic Event, and nothing guarantees that its target is an input element. The fix is to state the type explicitly in the class:

<input (input)="onNameInput($event)">
onNameInput(event: Event): void {
  const input = event.target as HTMLInputElement;
  this.name = input.value;
}

With as HTMLInputElement we tell the compiler "this is an input, trust me." Strict mode can feel annoying at first, but it exists precisely to catch these "it worked on my machine" bugs before they reach the browser.

Template Reference Variables: #ref

Most of the time you don't even need to deal with $event. You can give an element a name inside the template:

<input #cityInput placeholder="Şehir">
<button (click)="setCity(cityInput.value)">Kaydet</button>
setCity(value: string): void {
  this.city = value;
}

#cityInput is a label that lets you reach the input element itself from within the template. Angular knows this variable is an HTMLInputElement, so cityInput.value compiles fine in strict mode. The variable only lives inside that template; the class can't see it.

One more shortcut: if you want to save when the user presses Enter, just write (keyup.enter)="setCity(cityInput.value)". Angular filters the key for you.

Two-Way Binding: [(ngModel)]

Form fields usually need both directions at once. The value in the class should show up in the input, and the value in the class should update as the user types:

<input [(ngModel)]="name">
<p>Merhaba, {{ name }}</p>

Every letter you type shows up in the paragraph instantly. But when you save this code, you will most likely see this error:

error NG8002: Can't bind to 'ngModel' since it isn't a known property of 'input'.

This is where the module logic we saw earlier comes in. ngModel is a directive and it lives in FormsModule. To use it, you have to add it to AppModule's imports:

import { FormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent,
    ProfileCardComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We will cover the rest of forms, meaning validation and form state, in upcoming posts. For now it's enough to know that ngModel sets up a two-way connection.

Banana in a Box: What Is [( )] Really?

In the Angular world the [()] syntax is called "banana in a box": two bananas inside a square box. If you ever forget which goes inside, remember the picture; the bananas are in the box, so square brackets first, then round ones.

The syntax isn't magic; it's shorthand for two familiar pieces. The line above is exactly equivalent to this:

<input [ngModel]="name" (ngModelChange)="name = $event">

The square-bracket part is a property binding that carries the value from the class to the input. The round-bracket part is an event binding that delivers the new value as $event whenever the input changes. Here $event is not a DOM event but the new text itself.

The rule isn't specific to ngModel. Any component with an input called x and an output called xChange can be used with [(x)]. We will cover adding inputs and outputs to our own components in upcoming posts.

A Small Experiment

Add an "online / offline" button to the profile card. Clicking it should flip isOnline, change the color of the status dot through [class.online], and update the button label with interpolation. Then make the role field editable with an [(ngModel)] input.

Finally, add two buttons that increase and decrease progress, and watch the bar grow and shrink through [style.width.%]. Use [disabled] to turn one of them off when progress reaches 100.

In this experiment we toggled a class based on a condition. But what if we want to remove an element from the screen entirely based on a condition, or repeat a list with a single line? That's exactly our next topic: Angular's built-in directives.