typescript - Angular Signals: Hur man destrukturerar modell


Jag har två Angular (v19) komponenter.

Jag vill ha en instans av ComponentB inuti mallen av ComponentA. ComponentB.parameters bör bindas till de individuella fälten i ComponentA på ett sådant sätt att en ändring av ComponentB.parameters uppdaterar de individuella fälten i ComponentA och en uppdatering av något av fälten i ComponentA bör uppdatera ComponentB.parameters.

Hur gör man detta?

a.component.ts

@Component({
    selector: 'app-a',
    templateUrl: './a.component.html',
    standalone: true,
    imports: [ComponentB]
})
export class ComponentA {
    public readonly filter = model<string>();
    public readonly orderby = model<string>();
    public readonly skip = model<number>();
    public readonly top = model<number>();
}

b.component.ts

@Component({
    selector: 'app-b',
    standalone: true,
})
export class ComponentB {
    public readonly parameters = model<Parameters>()
}

parameters.ts

export interface Parameters {
    filter?: string;
    orderby?: string;
    skip?: string;
    top?: string;
}

a.component.html

<app-b [(parameters)]="???"></app-b>

Svar #1

Vi kan uppdatera parametrarna för att innehålla de individuella signalerna, så att du kan binda dem till [(ngModel)] inne i barnkomponenten.

export interface Parameters {
  filter: ModelSignal<string>;
  orderby: ModelSignal<string>;
  skip: ModelSignal<number>;
  top: ModelSignal<number>;
}

Då kräver vi inte att barnkomponenten accepterar en model-signal, utan istället en input.required-signal. Jag använder required eftersom jag annars måste omsluta varje element som använder den interna signalen som filter inom ett if-villkor, eftersom den kan vara undefined, så input.required är enklare, men låt mig veta om du vill veta hur den koden skulle se ut.

@Component({
  selector: 'app-b',
  standalone: true,
  imports: [FormsModule],
  template: ` <br/><br/><br/>
  Child<br/>
    <input [(ngModel)]="parameters().filter"/><br/>
    <input [(ngModel)]="parameters().orderby"/><br/>
    <input [(ngModel)]="parameters().skip"/><br/>
    <input [(ngModel)]="parameters().top"/><br/>
  `,
})
export class ComponentB {
  public readonly parameters = input.required<Parameters>();
}

Nu kan vi helt enkelt skicka ett objekt som heter parameters som innehåller alla individuella signaler till barnet, och därmed har vi tvåvägsbindning mellan föräldra- och barnkomponenten.

@Component({
  selector: 'app-root',
  imports: [ComponentB, FormsModule],
  template: `
    Parent<br/>
    <input [(ngModel)]="filter"/><br/>
    <input [(ngModel)]="orderby"/><br/>
    <input [(ngModel)]="skip"/><br/>
    <input [(ngModel)]="top"/><br/>
    <br/><br/><br/>
    <app-b [parameters]="parameters"></app-b>
  `,
})
export class App {
  public readonly filter = model<string>('');
  public readonly orderby = model<string>('');
  public readonly skip = model<number>(1);
  public readonly top = model<number>(2);
  parameters: Parameters = {
    filter: this.filter,
    orderby: this.orderby,
    skip: this.skip,
    top: this.top,
  };
}

Fullständig kod:

import { Component, ModelSignal, input, model } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

export interface Parameters {
  filter: ModelSignal<string>;
  orderby: ModelSignal<string>;
  skip: ModelSignal<number>;
  top: ModelSignal<number>;
}

    @Component({
      selector: 'app-b',
      standalone: true,
      imports: [FormsModule],
      template: ` <br/><br/><br/>
      Child<br/>
        <input [(ngModel)]="parameters().filter"/><br/>
        <input [(ngModel)]="parameters().orderby"/><br/>
        <input [(ngModel)]="parameters().skip"/><br/>
        <input [(ngModel)]="parameters().top"/><br/>
      `,
    })
    export class ComponentB {
      public readonly parameters = input.required<Parameters>();
    }

@Component({
  selector: 'app-root',
  imports: [ComponentB, FormsModule],
  template: `
    Parent<br/>
    <input [(ngModel)]="filter"/><br/>
    <input [(ngModel)]="orderby"/><br/>
    <input [(ngModel)]="skip"/><br/>
    <input [(ngModel)]="top"/><br/>
    <br/><br/><br/>
    <app-b [parameters]="parameters"></app-b>
  `,
})
export class App {
  public readonly filter = model<string>('');
  public readonly orderby = model<string>('');
  public readonly skip = model<number>(1);
  public readonly top = model<number>(2);
  parameters: Parameters = {
    filter: this.filter,
    orderby: this.orderby,
    skip: this.skip,
    top: this.top,
  };
}

bootstrapApplication(App);

Stackblitz-demonstration

Svar #2

Att använda en beräknad i en A-komponent för att bygga parametrar och dela dubbelriktad bindning för att destrukturera parameterutgång.

a.component.ts

@Component({
    selector: 'app-a',
    templateUrl: './a.component.html',
    standalone: true,
    imports: [ComponentB]
})
export class ComponentA {
    public readonly filter = model<string>();
    public readonly orderby = model<string>();
    public readonly skip = model<number>();
    public readonly top = model<number>();
    protected parameters= computed(() => ({filter : filters(), orderby : orderby(), skip :skip(), top :top()}));

  protected onParametersChange(params: Parameters | undefined) {
    if (params) {
      filter.set(params.filter);
      orderby .set(params.orderby);
      skip .set(params.skip);
      top .set(params.top);
    }
  }
}

b.component.ts

@Component({
    selector: 'app-b',
    standalone: true,
})
export class ComponentB {
    public readonly parameters = model<Parameters>()
}

parameters.ts

export interface Parameters {
    filter?: string;
    orderby?: string;
    skip?: string;
    top?: string;
}

a.component.html

<app-b [parameters]="parameters()" (parametersChange)="onParametersChange($event)"></app-b>

Obs: fungerar eftersom filter, orderby, skip och top är primitiva, om det finns objekt, behöver du ändra lika signal-funktionen.