Skip to content
Angular ng components 4 min read

Input Properties

Components rarely live in isolation — a parent needs to feed data down to its children. In Angular, that one-way data flow from parent to child is expressed through inputs. Historically this meant the @Input() decorator on a class property; modern Angular (17.1+) adds a signal-based input() function that integrates cleanly with the reactivity system. This page covers both, plus required inputs, aliases, and value transforms.

Declaring an input with @Input

The classic approach is to decorate a public class field with @Input(). The field name becomes the attribute the parent binds to in the template.

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <article class="card">
      <h3>{{ name }}</h3>
      <p>{{ role }}</p>
    </article>
  `,
})
export class UserCardComponent {
  @Input() name = '';
  @Input() role = 'Member';
}

A parent binds to the inputs using square-bracket property binding. Anything inside [ ] is evaluated as an expression, so you can pass strings, objects, or component state:

<app-user-card [name]="user.fullName" [role]="user.title" />

To pass a literal string you can drop the brackets — name="Ada" binds the constant "Ada", whereas [name]="Ada" would try to read a property called Ada on the parent.

Signal-based inputs

Since Angular 17.1, the preferred style is the input() function. It returns a read-only signal, so the value participates in change detection automatically and can be consumed by computed() and effect() without extra wiring.

import { Component, input, computed } from '@angular/core';

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <article class="card">
      <h3>{{ name() }}</h3>
      <p>{{ greeting() }}</p>
    </article>
  `,
})
export class UserCardComponent {
  name = input('');                 // InputSignal<string>
  role = input('Member');

  greeting = computed(() => `${this.name()} — ${this.role()}`);
}

Note the call syntax in the template: signal inputs are read with name(), not name. The parent binding is unchanged — [name]="user.fullName" works exactly the same way regardless of which API the child uses.

Required inputs

A signal input can be marked required, which makes the binding mandatory. If a parent renders the component without supplying it, the compiler reports an error at build time rather than silently passing undefined.

export class UserCardComponent {
  userId = input.required<string>();
  name = input('');
}

With the decorator API the equivalent is @Input({ required: true }) userId!: string;. Prefer input.required() in new code — the type is non-nullable, so you never have to guard against undefined.

Aliases

Sometimes the public binding name should differ from the internal property name — for example to match an HTML attribute convention or to avoid a naming clash. Use an alias.

export class UserCardComponent {
  // Parent binds [user-id]; component reads this.id()
  id = input.required<string>({ alias: 'user-id' });
}
<app-user-card [user-id]="user.id" />

The decorator form takes the alias as a string argument: @Input('user-id') id!: string;.

Aliases are easy to overuse. They add a layer of indirection that makes templates harder to trace back to the component class. Reach for one only when there’s a real reason — keeping the property and binding names identical is almost always clearer.

Input transforms

A transform lets you normalise or coerce an incoming value before the component stores it. The most common use is coercing string-y attribute values into the type you actually want. Angular ships two ready-made transforms, booleanAttribute and numberAttribute.

import { Component, input, booleanAttribute, numberAttribute } from '@angular/core';

@Component({
  selector: 'app-toggle',
  standalone: true,
  template: `<button [disabled]="disabled()">{{ count() }}</button>`,
})
export class ToggleComponent {
  // Bare attribute `disabled` now means `true`
  disabled = input(false, { transform: booleanAttribute });
  count = input(0, { transform: numberAttribute });
}
<!-- No brackets, no value: booleanAttribute resolves this to true -->
<app-toggle disabled count="5" />

Output:

The button renders disabled with the label "5".

You can also supply your own transform — any pure function from the accepted input type to the stored type:

title = input('', { transform: (value: string) => value.trim().toUpperCase() });

Reacting to input changes

With signal inputs, reacting to a change is just reading the signal inside a computed() or effect(). With the decorator API you implement the OnChanges lifecycle hook.

import { Component, input, effect } from '@angular/core';

@Component({ selector: 'app-logger', standalone: true, template: '' })
export class LoggerComponent {
  value = input(0);

  constructor() {
    effect(() => console.log('value changed:', this.value()));
  }
}

API comparison

Feature@Input() decoratorinput() signal
Read in template{{ name }}{{ name() }}
Required@Input({ required: true })input.required<T>()
Alias@Input('alias')input(d, { alias })
Transform@Input({ transform })input(d, { transform })
Reactivitymanual / OnChangesautomatic (signal)
Writablemutable fieldread-only

Best Practices

  • Prefer the signal input() API in new standalone components — it removes OnChanges boilerplate and composes with computed()/effect().
  • Use input.required<T>() for values the component genuinely cannot function without; it gives you a non-nullable type and a compile-time guarantee.
  • Treat inputs as read-only. Never mutate an input from inside the child; derive new state with computed() instead.
  • Provide sensible default values for optional inputs so the component renders safely before bindings arrive.
  • Use booleanAttribute and numberAttribute transforms for attribute-style bindings rather than parsing strings by hand.
  • Avoid aliases unless an external naming convention forces one — matching property and binding names keeps templates legible.
Last updated June 14, 2026
Was this helpful?