javascript - Beräknade signalberoenden


Fråga till Angular-experterna om Signals:

När du skapar en computed()-signal spåras beroenden automatiskt, men enligt dokumentationen endast för signaler som tidigare har lästs.

Om jag förstår detta korrekt, skulle följande vara problematiskt eftersom every potentiellt kan bryta tidigt om ett av de första objekten inte är valt, vilket gör att den beräknade funktionen bara spårar en delmängd av objekten?

const allSelected = computed(() => {     
    return selectableItems.every(item => item.selected()); 
});

För att vara tydlig: item.selected() är också en signal.

Det verkar fungera bra, men jag vill vara säker.

Svar #1

Ditt särskilda problem borde inte vara ett problem. För att selectableItems ska vara true måste alla objekt utvärderas. Om ett objekt blir false spelar det ingen roll om ett objekt med ett högre index blir false i framtiden eftersom resultatet av every logiskt sett fortfarande blir detsamma.

Det sagt, kommer du att ha problem om selectableItems kan få objekt tillagda eller borttagna.

Ett extremt exempel är om selectableItems är tomt när computed anropas. Vid det tillfället kommer inga signaler att utlösas och värdet kommer aldrig att kontrolleras igen.

Ett mindre extremt problem skulle vara om ett objekt läggs till vid ett senare tillfälle. Värdet på allSelected kommer inte att utvärderas förrän ett av de tidigare objekten ändrar sitt värde. Om alla gamla objekt är true och det nya objektet är false, kommer allSelected att vara fel.

Om selectableItems kan ändras, kan en bättre design vara att göra selectableItems till en signal istället för alla individuella objekt, och sedan använda update när ett objekt ändras.

// changed selectableItems to items to make example shorter.
items = signal([] as SelectableItem[]);

const allSelected = computed(() => this.items().every(item => item.selected));
addItem(item: SelectableItem) {
  this.items.update(x => [...x, item]);
}
updateItem(item: SelectableItem) {
  this.items.update(x => {
    const index = x.indexOf(item);
    return [...x.slice(0, index), item, ...x.slice(index + 1, 0)];
  });
}

Avancerad Lösning

Om du ville hålla logiken för din array utanför din komponent kunde du till och med skapa din egen signal. Lösningen nedan lägger till metoder till en skrivbar signal och använder odokumenterade men exporterade funktioner från kärnsignalprimitiver.

export interface ArraySignal<T> extends WritableSignal<T[]> {
  updateElement(item: T, updateFn: (x: T) => T): void;
  push(item: T): void;
}

export function arraySignal<T>(initialValue: T[]): ArraySignal<T> {
  const internal = signal(initialValue) as SignalGetter<T[]> & WritableSignal<T[]>;
  const node = internal[SIGNAL];
  return Object.assign(internal, {
    push: (item: T) => signalMutateFn(node, (x) => {
      x.push(item);
      return x;
    }),
    updateElement: (item: T, updateFn: (x: T) => T) => {
      signalMutateFn(node, (x) => {
        const index = x.indexOf(item);
        (index !== -1) && (x[index] = updateFn(item));
      })
    }
  })
}