#How do I update a signal once input signal has been changed

18 messages · Page 1 of 1 (latest)

supple sluice
#

I made a multi select component that has an input() signal to set an Array of selectable options. I also have a writable signal to track which options have been selected. How do I clear the writable signal once the input of selectable options have been changed?

The only solution I can think of is an effect but the docs say that you should not write to signals in an effect. Is this even possible with the effects API or do I have to rewrite my component with ngOnChanges and traditional Input selectors?

import { Component, computed, effect, EventEmitter, input, Output, signal } from '@angular/core';

export interface MultiSelectGroupOption {
  id: number;
  title: string;
}

@Component({
  selector: 'app-multi-select-group',
  templateUrl: './multi-select-group.component.html',
})
export class MultiSelectGroupComponent {
  public maxSelectedItems = input<number>(1);
  public options = input<Array<MultiSelectGroupOption>>([]); // when this changes selectedItems needs to be reset
  public selectedItems = signal<Array<MultiSelectGroupOption>>([]);
  @Output()
  public selectionChange = new EventEmitter<Array<MultiSelectGroupOption>>();

  public constructor() {
    effect(() => {
      this.selectionChange.emit(this.selectedItems());
    });
  }

  public selectItem(item: MultiSelectGroupOption): void {
      this.selectedItems.set([...selectedItems, item]);
  }
tulip geyser
#

Use model input, read the docs

supple sluice
tulip geyser
#

yes but you can not change the state inside the untracked

#

it will handle with perfomance bailout

#

just read

verbal ruin
#

This would be a reasonable use case for effect, imo.

#

I would probably write it as:

effect(() => this.selectionChange.emit(this.selecteditems());
effect(() => {
  this.options();
  this.selectedItems.set([]);
}, allowSignalWrites: true);
median narwhal
#
<div>{{ value() }} displays "abc"</div>
<div>{{ value2() }} displays "abcdef"</div>
value = input('abc');
value2 = computed(() => value() + 'def'); // you need to compute the input value, then use this.

constructor() { 
  effect(() => console.debug(value())); // you want to react on changes, then use this.
}

if you want to pass events to the child, then use services and config the provider.

verbal ruin
#

Ooo. I don't know whether this is magical or terrifying.

#
options = input<...[]>([]);
optionsWithSelection = computed(() => {options: options(), selected(): signal([])});
selectedItems = computed(() => this.optionsWithSelection().selected());

effect(() => this.selectionChange.emit(this.selectedItems());

selectItem(item) {
  this.optionsWithSelection().selected.update(items => [...items, item]);
}
supple sluice
verbal ruin
supple sluice
verbal ruin
#

So you don't need the prevOptions thing at all

supple sluice
#
  public constructor() {
    let prevOptions: Array<MultiSelectGroupOption> = [];

    effect(() => {
      if (!this.resetOnChange()) {
        return;
      }
      const options = this.options();
      untracked(() => {
        if (prevOptions !== options) {
          prevOptions = options;
          this.selectedItems.set([]);
        }
      });
    });
  }