Angular refactor Output API

Starting in Angular version 17.3, we now have a new output API. Note that the new output is NOT a signal. So why did the Angular team define a new output API as part of its signal-ification of Angular?

import { Component, EventEmitter, Output, effect, output, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-filter',
  standalone: true,
  imports: [FormsModule],
  template: `
  <div class='card'>
    <div class='card-body'>
      <div class='row'>
        <div class='col-sm-2'>Filter by:</div>
        <div class='col-sm-5'><input type='text' [(ngModel)]='filter' /></div>
      </div>
    </div>
  </div>
  `
})
export class FilterComponent {
  filter = signal('');
  // @Output() processFilter = new EventEmitter<string>();
  processFilter = output<string>({
    alias: 'filterChange'
  });

  filterEff = effect(() => this.processFilter.emit(this.filter()));
}

Now our parent snacks component HTML

<div class="card border-secondary">
  <div class="card-header text-secondary fw-bold">
    {{ pageTitle }}
  </div>

  <div class="card-body">
    <app-filter (filterChange)="filterList($event)"/>
    <div class="row">
      <div class="col-md-4 col-sm-4 fw-bold">Name</div>
      <div class="col-md-4 col-sm-4 fw-bold text-center">Price</div>
    </div>

    @for (snackItem of filteredSnacks(); track snackItem) {
    <div>
      <div>
        <div class="row mb-2">
          <div class="col-md-4 col-sm-4">
            {{ snackItem.name }}
          </div>
          <div class="col-md-4 col-sm-4 text-center">
            {{ snackItem.price | currency }}
          </div>
        </div>
      </div>
    </div>
    } @empty { No snacks available that match the filter }
  </div>
</div>

Ts code

import { Component, computed, inject, signal } from '@angular/core';
import { FilterComponent } from '../filter/filter.component';
import { SnackService } from './snack.service';
import { CurrencyPipe } from '@angular/common';

@Component({
  selector: 'app-snack',
  standalone: true,
  imports: [ FilterComponent, CurrencyPipe],
  templateUrl: './snack.component.html'
})
export class SnackComponent {
  pageTitle = 'Snacks';
  listFilter = signal('');

  snackService = inject(SnackService);

  snacks = this.snackService.snacks;
  filteredSnacks = computed(() =>
    this.snacks().filter(s => s.name.includes(this.listFilter())));

  filterList(filterText: string) {
    this.listFilter.set(filterText);
  }
}

Stackblitz demo

Leave a Reply

Your email address will not be published. Required fields are marked *