angularsignalsangular-signalsangular18angular-input

How to read the array in an Angular signal when it changes to an input signal?


Note that this is using Angular 18.0.0.

I am updating code to change from an observer to a signal. Instead of watching the observer, I want it passed into the component as if it were @Input. As an intermediate step, I can use a signal this way, with an empty initial value. When an array of several accounts (from an observer) is set to the accounts signal, the total is summed and it works.

accounts = signal(<Account[]>[]);
totalLiquidationValue = computed(() => {
  return this.accounts().reduce((a, acct) => (a += acct.liquidationValue), 0);
});

But if I try to use it as an input signal, it does not even compile:

accounts = input.required<Account[]>;

The error is:

Property 'reduce' does not exist on type '(opts?: InputOptionsWithoutTransform<Account[]> | undefined) => InputSignal<Account[]>'.

I also cannot do this in the template after changing the signal to an input signal:

 *ngFor="let a of accounts()"

Using a single object (not an array) works elsewhere in the application. It's when I try to use an array that things don't compile. What is the difference between a standalone signal and an input signal in this situation?


Solution

  • You are missing to initialize the signal with accounts = input.required<any[]>().

    import { Component } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import 'zone.js';
    
    import { computed, input } from '@angular/core';
    
    @Component({
      selector: 'app-child',
      standalone: true,
      imports: [],
      template: `
      @for (account of accounts(); track account.name) {
        <div>{{account.name}}</div>
      }
      <div>Total: {{totalLiquidationValue()}}</div>
      `
    })
    export class ChildComponent {
      accounts = input.required<any[]>();
      totalLiquidationValue = computed(() => {
        return this.accounts().reduce(
          (a: any, acct: any) => (a += acct.liquidationValue),
          0
        );
      });
    }
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [ChildComponent],
      template: `
        <app-child [accounts]="accounts"/>
      `,
    })
    export class App {
      accounts = [
        { name: 'test', liquidationValue: 1 },
        { name: 'test2', liquidationValue: 2 },
        { name: 'test3', liquidationValue: 3 },
      ];
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo