DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on • Edited on

Angular 19 Signals with input() and output()

Angular input() / output() Preview

Angular 19 Signals with input() and output()

With Angular 16+, the Composition API offers a modern and reactive way to manage component state using signal(), input(), and output(). In Angular 19, this approach is more powerful than ever, enabling lightweight and reactive component communication.

In this guide, we'll build a Product Dashboard using:

  • input() and output() for reactive component I/O
  • signal() for local state management
  • Standalone components with full signal-based communication

Folder Structure

/components
  ├── product-page/
  │   └── product-page.component.ts
  ├── product-add/
  │   └── product-add.component.ts
  ├── product-list/
  │   └── product-list.component.ts
Enter fullscreen mode Exit fullscreen mode

1. product-add.component.ts – Emits new product using output()

import { Component, output, signal } from '@angular/core';
export interface Product {
  id: number; name: string; price: number;
}
@Component({
  selector: 'product-add',
  standalone: true,
  template: `
    <h3>Add Product</h3>
    <input type="text" placeholder="Name" [value]="name()"  
     (input)="name.set(txtName.value)" #txtName />
    <input type="number" placeholder="Price" [value]="price()"
     (input)="price.set(+txtPrice.value)" #txtPrice />
    <button (click)="addProduct()">Add</button>
  `,
})
export class ProductAddComponent {
  name = signal('');
  price = signal(0);
  productAdded = output<Product>();
  addProduct() {
    if (!this.name || this.price() <= 0) {
      return;
    }
    const productAdded: Product = {
      id: Math.floor(Math.random() * 1000),
      name: this.name(),
      price: this.price(),
    }
    this.productAdded.emit(productAdded);
    this.resetFields();
  }
 resetFields() {
   this.name.set('');
   this.price.set(0);
 }
}
Enter fullscreen mode Exit fullscreen mode

2. product-list.component.ts – Consumes products with input()

import { Component, input } from '@angular/core';
import { Product } from '../product-add/product-add.component';

@Component({
  selector: 'product-list',
  standalone: true,
  template: `

  <h3>Product List</h3>
  <h1>Si cargo </h1>
  <ul>
    @for (product of products(); track product.id; let idx = $index) {
      <li>
        {{ product.name }} - ${{ product.price }}
      </li>
    }
  </ul>


  `
})
export class ProductListComponent {
  products = input.required<Product[]>();
}
Enter fullscreen mode Exit fullscreen mode

3. product-page.component.ts – Orchestrates everything with signal()

import { Component, signal } from '@angular/core';
import { ProductAddComponent, Product } from '../product-add/product-add.component';
import { ProductListComponent } from '../product-list/product-list.component';

@Component({
  selector: 'product-page',
  standalone: true,
  imports: [ProductAddComponent, ProductListComponent],
  template: `
    <h2>Product Dashboard</h2>
    <product-add (productAdded)="addProduct($event)"></product-add>
    <product-list [products]="products()"></product-list>
  `
})
export class ProductPageComponent {
  products = signal<Product[]>([]);

  addProduct(newProduct: Product) {
    this.products.update(p => [...p, newProduct]);
  }
}
Enter fullscreen mode Exit fullscreen mode

What This Shows

Feature Demonstrated
input.required<T>() In ProductListComponent
output<T>() In ProductAddComponent
signal<T>() In ProductPageComponent
Reactive flow Between child and parent
Standalone components

Top comments (0)