Property Binding
Property binding lets you set the value of a DOM element’s property from your component’s data. Wrapping a target in square brackets — [property] — tells Angular to evaluate the right-hand side as a TypeScript expression and assign the result to the underlying DOM property. It is one-way binding: data flows from the component into the view, keeping the rendered UI in sync whenever the source value changes. This is the idiomatic way to drive src, disabled, value, and any custom component @Input() in modern Angular.
How property binding works
The syntax is a target name in square brackets, followed by = and a template expression in quotes. Angular reads the expression in the context of the component class, then writes the result to the matching DOM property on every change-detection pass.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-profile',
standalone: true,
template: `
<img [src]="avatarUrl()" [alt]="userName()" [width]="size()" />
<button [disabled]="!canEdit()">Edit</button>
`,
})
export class ProfileComponent {
avatarUrl = signal('https://cdn.example.com/u/42.png');
userName = signal('Ada Lovelace');
size = signal(96);
canEdit = signal(false);
}
Because the bound expressions are signals, reading them inside the template registers a dependency. When canEdit.set(true) runs later, Angular updates only the disabled property of that button — no manual DOM work required.
Property bindings are one-way. To react to changes the other direction (user input flowing back to your component), reach for event binding or two-way binding instead.
DOM properties vs HTML attributes
This is the single most common source of confusion. Attributes initialize the DOM and are defined by HTML; they are essentially static strings written in markup. Properties are the live, in-memory state of the DOM node, owned by the JavaScript object. Once the browser parses the page, the property is the source of truth — and property binding writes to the property, not the attribute.
Consider an <input>. The value attribute sets only the initial value. After a user types, the attribute still reads the original string while the value property holds the current text. Angular’s [value]="..." binds the property, so it always reflects your component state.
| Aspect | HTML attribute | DOM property |
|---|---|---|
| Defined by | The HTML specification / markup | The DOM object in memory |
| Lifetime | Static initializer | Live, mutable state |
| Read via | el.getAttribute('value') | el.value |
| Angular syntax | [attr.colspan]="2" | [colspan]="2" (if a property exists) |
| Example | disabled attribute string | disabled boolean property |
Most of the time you want a property, and most native properties have a matching binding target. A few cases have no corresponding DOM property — colspan, aria-*, and SVG attributes among them. For those, use attribute binding with the attr. prefix.
<!-- No `colspan` DOM property exists, so bind the attribute -->
<td [attr.colspan]="columnSpan()">Totals</td>
<!-- ARIA and data attributes follow the same rule -->
<button [attr.aria-label]="label()">Save</button>
Gotcha:
[colspan]="2"silently fails because there is nocolspanproperty to write. Always use[attr.colspan]for attribute-only targets. Angular will throw a template error if you bind a property that does not exist on a known element.
Binding to component inputs
Property binding is also how you pass data into child components. Any field decorated with @Input() — or declared with the signal-based input() — becomes a valid binding target.
import { Component, input } from '@angular/core';
@Component({
selector: 'app-badge',
standalone: true,
template: `<span class="badge">{{ count() }}</span>`,
})
export class BadgeComponent {
count = input.required<number>();
}
<!-- Parent template: the value flows into the child input -->
<app-badge [count]="unreadCount()" />
Expressions, not statements
The right-hand side is a template expression: it should be side-effect free and cheap to evaluate, because change detection may run it many times. Avoid assignments, new, increment operators, or chaining with ;.
<!-- Good: pure expression -->
<progress [value]="completed()" [max]="total()"></progress>
<!-- Avoid: calling expensive methods on every check -->
<div [title]="computeTooltip()"></div>
Prefer signals or computed() over methods so Angular can memoize the result instead of recomputing on every cycle.
Best practices
- Use
[property]for DOM properties and reserve[attr.name]strictly for attribute-only targets likecolspan,aria-*, and SVG. - Drive bindings from signals or
computed()values so updates are fine-grained and change detection stays cheap. - Keep binding expressions pure and short — move logic into the component class or a
computed. - Bind booleans directly to properties such as
[disabled]and[hidden]; never bind the attribute for these, as a present attribute is always truthy. - Use
input()/input.required()for child component inputs to get type safety and clearer required semantics. - When in doubt about whether a target is a property, let the Angular compiler guide you — it errors on unknown property bindings for known elements.