Tutorials Logic, IN info@tutorialslogic.com

Data Binding in Angular Two Way Binding

Binding Directions

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)].

String Interpolation

String Interpolation
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';
}

Property Binding

Property Binding
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;
}

Event Binding

Event Binding
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);
  }
}

Two-Way Binding

Two-Way Binding
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';
}

Signals and Template State

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.

  • signal(value) - Creates a writable signal.
  • computed(() => ...) - Creates a derived read-only signal.
  • effect(() => ...) - Runs a side effect when signals change.

Binding Targets

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 Contracts

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.

Explicit Validated Update

Explicit Validated Update
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.

Binding Diagnosis

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.
Confirm the page outcome

Binding Review

5 checks
  • I can distinguish text, property, attribute, class, style, event, and two-way bindings.
  • I can identify whether a binding targets the DOM, a directive, or a child component contract.
  • I can decide between ngModel, component model binding, and explicit input/event bindings.
  • I can diagnose missing imports, boolean attributes, stale object references, and mistyped events.
  • I keep template expressions fast and derive reactive display state with computed or pure pipes.

Binding Review Questions

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.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.