Data binding connects component state and template behavior. Interpolation displays text, property binding sets DOM or component properties, event binding reports user actions, and two-way binding combines input and output only when shared ownership is intentional.
A binding connects a template expression to text, a DOM property, an attribute, a class, a style, or an event. Interpolation and property binding move values from component state to the view; event binding reports user or browser activity back to the component.
Bind a DOM property when the browser exposes one, such as disabled, value, or src. Use attr. for attributes without a corresponding writable property, including many ARIA attributes. Class and style bindings update presentation without constructing class strings manually.
Two-way binding is property input plus change output. ngModel provides that pair for form controls after FormsModule is imported. A component model() field exposes the matching value and valueChange contract for [(value)].
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<h1>{{ title() }}</h1>
<p>2 + 2 = {{ 2 + 2 }}</p>
<p>{{ greeting.toUpperCase() }}</p>
`
})
export class AppComponent {
title = signal('Angular Templates');
greeting = 'hello world';
}
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<img [src]="logoUrl" [alt]="logoAlt" />
<button [disabled]="isDisabled()">Submit</button>
<p [class.highlight]="isActive">Highlighted text</p>
`,
styles: ['.highlight { background: yellow; }']
})
export class AppComponent {
logoUrl = 'https://angular.io/assets/images/logos/angular/angular.svg';
logoAlt = 'Angular Logo';
isDisabled = signal(false);
isActive = true;
}
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<button (click)="increment()">Click me</button>
<p>Clicked {{ count() }} times</p>
<input (keyup.enter)="onEnter($event)" placeholder="Press Enter" />
`
})
export class AppComponent {
count = signal(0);
increment() { this.count.update(c => c + 1); }
onEnter(event: Event) {
console.log((event.target as HTMLInputElement).value);
}
}
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [FormsModule],
template: `
<h2>Welcome: {{ userName }}</h2>
<input type="text" [(ngModel)]="userName" placeholder="Enter your name" />
`
})
export class AppComponent {
userName = 'Angular';
}
Calling a signal in a template lets Angular track that reactive dependency. When the signal changes, Angular knows which views need an update. Use computed for values derived from other signals instead of storing and synchronizing a duplicate value.
Template expressions should stay fast and free of visible side effects because Angular may evaluate them during checks. Move expensive transformations to computed state or a pure pipe, and move actions to event handlers.
Do not call a method that allocates a new array or object from a hot template binding unless that work is intentionally repeated. Stable state and a stable @for track key prevent unnecessary DOM replacement.
Square-bracket syntax can target a native DOM property, a component input, a directive input, an HTML or SVG attribute, a CSS class, or a style property. Choose the target by ownership rather than by visual similarity in the markup.
When an attribute binding evaluates to null, Angular removes the attribute. Class and style object bindings require a new array or object reference when their contents change.
| Syntax | Target |
|---|---|
| {{ total() | currency }} | Converts an expression to text and renders it. |
| [disabled]="saving()" | Sets the disabled property on HTMLButtonElement. |
| [value]="selectedId()" | Sets a native property or a matching component/directive input. |
| [attr.colspan]="columns()" | Sets an attribute when no matching writable DOM property exists. |
| [aria-label]="actionLabel()" | Binds an ARIA attribute using Angular's supported ARIA binding syntax. |
| [class.active]="selected()" | Adds or removes one CSS class. |
| [class]="classMap()" | Sets multiple classes from a string, array, or object. |
| [style.width.px]="width()" | Sets one style property and appends the declared unit. |
| (keydown.enter)="save()" | Runs a statement when the filtered event occurs. |
Two-way syntax is shorthand for a property binding and a matching Change event. Use it when both sides intentionally share ownership; use separate input and event bindings when validation or command handling must remain explicit.
| Syntax | Expanded contract |
|---|---|
| [(ngModel)]="name" | [ngModel]="name" plus (ngModelChange)="name = $event"; requires FormsModule. |
| [(value)]="volume" | [value]="volume" plus (valueChange)="volume = $event" on a component. |
| value = model(0) | Declares a writable ModelSignal and the implicit valueChange output. |
| [(value)]="volumeSignal" | Passes a writable signal instance directly to a component model binding. |
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-age-field',
template: `
<input
type="number"
[value]="age()"
(input)="acceptAge($event)"
aria-label="Age"
/>
`
})
export class AgeField {
age = signal(18);
acceptAge(event: Event): void {
const next = Number((event.target as HTMLInputElement).value);
this.age.set(Math.min(120, Math.max(0, next)));
}
}
Separate bindings make the normalization step visible. Two-way binding would hide the rule that clamps the value between 0 and 120.
When a binding appears stale or fails compilation, identify its owner first: DOM property, attribute, directive, component input, or event. The error usually points to a missing import, wrong public name, incorrect value type, or state that never changed reactively.
| Symptom | Check |
|---|---|
| Unknown property or element | Import the standalone dependency or the NgModule that exports it; then verify the selector and input name. |
| disabled="false" still disables | Use [disabled]="false" because a boolean attribute is active whenever it is present. |
| Class or style object mutation is ignored | Replace the array or object with a new reference, or bind one class/style property directly. |
| Event target has no value property | Narrow event.target to HTMLInputElement at the handler boundary. |
| Template method runs repeatedly | Move derived work to computed or a pure pipe and keep event handlers responsible for actions. |
| Child value changes but parent does not | Verify the child emits the matching nameChange output or declares name with model. |
disabled is a boolean HTML attribute: its presence means the control is disabled, regardless of the text assigned to it. Writing disabled="false" still places the attribute on the element.
Two-way binding is convenient for local form state, but it can hide where an important change came from. Prefer separate [value] and (input) bindings when the update needs validation, normalization, analytics, or an explicit command before state changes.
Angular types $event as Event, whose target property is only EventTarget and does not promise an input value. The handler knows the event came from an input, so it narrows the target to HTMLInputElement before reading value. Keep that cast close to the event boundary and avoid $any unless a library’s typing leaves no practical alternative.
Explore 500+ free tutorials across 20+ languages and frameworks.