Property Binding
Property binding sets a property of an element or component from your data: enable a button, set an image, pass data to a child, toggle a class. Square brackets mean “this value comes from the class”.
On this page · 5 min · Beginner
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Beginner
- Updated
- Oct 28, 2026
Learning objectives#
- Bind DOM properties like disabled, src and value
- Pass data to child component inputs
- Bind attributes, classes and styles
- Know the difference between properties and attributes
Explanation#
[property]="expression" sets a DOM property: [disabled]="saving()", [src]="photoUrl", [value]="query".
The same syntax passes data to a child component’s input: <app-product-card [price]="p.price" />.
Some HTML attributes have no matching DOM property (aria-*, colspan, data-*). Use [attr.aria-label]="label".
Toggle one class with [class.active]="isActive()"; set several with [class]="{ active: isActive(), error: hasError() }". Styles: [style.width.px]="width" or [style.color]="color".
Without brackets, the attribute value is a plain string: price="20" passes the text "20", while [price]="20" passes the number 20.
Syntax#
<button [disabled]="saving()">Save</button>
<img [src]="photoUrl" [alt]="name" />
<app-rating [stars]="4" />
<td [attr.colspan]="span">…</td>
<li [class.active]="selected()" [style.font-weight]="bold ? 'bold' : 'normal'">…</li>
Code example: A progress card driven by property bindings#
import { Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-upload-card',
template: `
<article [class.done]="isDone()" [class.error]="failed()">
<img [src]="iconUrl()" [alt]="fileName + ' icon'" width="32" height="32" />
<strong>{{ fileName }}</strong>
<div class="bar" [attr.aria-valuenow]="progress()" role="progressbar" aria-valuemin="0" aria-valuemax="100">
<div class="fill" [style.width.%]="progress()"></div>
</div>
<button [disabled]="isDone() || failed()" (click)="advance()">Upload 25%</button>
<button [hidden]="!failed()" (click)="retry()">Retry</button>
</article>
`,
styles: `
.bar { background: #eee; height: 8px; width: 200px; }
.fill { background: #4f46e5; height: 8px; }
.done { border-left: 4px solid green; padding-left: 8px; }
.error { border-left: 4px solid red; padding-left: 8px; }
`,
})
export class UploadCard {
fileName = 'invoice.pdf';
progress = signal(0);
failed = signal(false);
isDone = computed(() => this.progress() >= 100);
iconUrl = computed(() => (this.isDone() ? 'https://img.icons8.com/color/32/checked.png' : 'https://img.icons8.com/color/32/pdf.png'));
advance() {
if (this.progress() === 50 && !this.failed()) { this.failed.set(true); return; } // simulate one failure
this.progress.update(p => Math.min(100, p + 25));
}
retry() {
this.failed.set(false);
this.progress.update(p => p + 25);
}
}
Each “Upload 25%” click widens the purple bar. At 50% the card gets a red border, the Upload button is disabled and Retry appears. Retry continues; at 100% the border turns green, the icon changes to a tick and the Upload button is disabled. Screen readers get the current value through aria-valuenow.
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- [class.done] and [class.error] toggle single classes from signals.
- [src] swaps the icon when the computed isDone() changes.
- [attr.aria-valuenow] sets an attribute that has no DOM property.
- [style.width.%] sets the width with a unit suffix.
- [disabled] and [hidden] enable, disable and hide buttons from state.
Real-world use case#
Data tables bind [class.selected] on rows, [disabled] on bulk-action buttons, and [attr.aria-sort] on column headers for accessible sorting.
Common mistakes#
[aria-label]="x" fails because there is no ariaLabel binding target in some cases; use [attr.aria-label].
[disabled]="false" is false; disabled="false" is the string "false" — and the button is disabled!
Many [style] bindings make templates noisy. Prefer classes.
Best practices#
- Brackets for anything that is not a fixed string
- [attr.] for aria and data attributes
- [class.x] for toggles, CSS for the look
- Bind to computed signals for derived values
Remember this#
[prop]="expr" = set from the class.
No brackets = plain string.
[attr.x], [class.x], [style.x.unit] for special cases.
Practice exercise#
Your task: Show a list of menu items where the current one has the class "active" and aria-current="page".
Need a hint?
Show solution
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-menu',
template: `
<nav>
@for (item of items; track item) {
<a href="#" [class.active]="item === current()" [attr.aria-current]="item === current() ? 'page' : null"
(click)="current.set(item); $event.preventDefault()">{{ item }}</a>
}
</nav>
`,
styles: `.active { font-weight: bold; }`,
})
export class Menu {
items = ['Home', 'Shop', 'Contact'];
current = signal('Home');
}
Mini quiz#
Which binding sets an ARIA attribute?
Want more? Practise all Angular MCQs · Angular interview questions
My notes#
Saved in this browser.