Daniel
Daniel

Reputation: 311

How to set a new value for a 'Signal' that is called from an 'Effect'?

I'm working with signals in Angular 17 and I want to update the value of a signal, but it throws this error:

NG0600: Writing to signals is not allowed in a `computed` or an `effect` by default. Use `allowSignalWrites` in the `CreateEffectOptions` to enable this inside effects.

I've been looking for information about it but haven't found anything useful.

Basically what I'm trying to do is update the signal value after a condition, this is all called from the Effect method:

Component:

export class LibGridViewComponent {
    private _libSubjectsGiproService = inject(LibSubjectsGiproService)

    constructor() {
    effect(() => {
      this.getValuesBySearchSignal()
    });
  }
  
  getValuesBySearchSignal(displaySource?: any): any[] {
    let testArr = [
      {
        "name": "B179 HEPATITIS VIRAL AGUDA NO ESPECIFICADA",
        "value": "B179"
      },
      {
        "name": "B980 HELICOBACTER PYLORI [H.PYLORI] COMO LA CAUSA DE ENFERMEDADES CLASIFICADAS EN OTROS CAPITULOS",
        "value": "B980"
      },
      {
        "name": "C799 TUMOR MALIGNO SECUNDARIO - SITIO NO ESPECIFICADO",
        "value": "C799"
      },
    ]

    let searchTerm = this._libSubjectsGiproService.getSearchTerm()
    
    let foundSource:any = []

    if (searchTerm && searchTerm != '') {
      foundSource = testArr.filter(i => i.name.toLowerCase().includes(searchTerm));

        let currentSource = this.initialDisplaySource.value;
        this.initialDisplaySource.next([...currentSource, ...foundSource]);
        this._libSubjectsGiproService.setSearchTerm('')
    } else {
      this.initialDisplaySource.next([])
    }

    return this.initialDisplaySource.value
  }
}

Service:

export class LibSubjectsGiproService {
    searchTerm: WritableSignal<any> = signal<any>('');
    
    setSearchTerm(value: any) {
    this.searchTerm.set(value);
  }

  getSearchTerm() {
    return this.searchTerm();
  }
}

UPDATE: I'm gonna try to add more context to what I'm trying to do, I have a grid view and one of its columns is a Select with the option to search for the item based on user input. It is possible to add more rows to the table, but the entry with which the elements are searched remains with the previously entered value, the idea is that each time a new row is added the signal is empty, therefore initialDisplaySource should also be empty until the user starts typing.

Upvotes: 2

Views: 3158

Answers (2)

Joel Garcia Nu&#241;o
Joel Garcia Nu&#241;o

Reputation: 933

Warning: effect is currently in developer preview

Code sample about how to enable the flag of "allowSignalsWrite":

  constructor() {
    effect(() => {
      this.getValuesBySearchSignal()
    }, { allowSignalWrites: true } );
  }

It isn't recommended and is not enabled by default because calling a signal could create a cycle that causes the browser to freeze.

This could result in a significant issue that is hard to detect as the application grows.

Update 10/07/2024 Communicate of angular:

  • As a result, we’ve decided to remove the allowSignalWrites flag entirely in v19, and allow effects to set signals by default. (still in developer preview)

Upvotes: 1

Naren Murali
Naren Murali

Reputation: 57696

Although this can be done with effect this might not be the best solution, since updating the signal will trigger the effect and you have to do the additional work of setting the signal again, which in the docs is frowned upon, instead go for the html events that happen only when the user changes the input.

If you are certain you want the effect method, then go for the other suggestion in my answer.

Basically we trigger the function getValuesBySearchSignal when the user types on the select, since you have not shared the HTML, we can use (ngModelChange), (change), (input) this events can trigger the function and you can reset the signal and do anything else.

<input [(ngModel)]="" (ngModelChange)="getValuesBySearchSignal()"/>

We can just use allowSignalWrites as true in the effect options.

  constructor() {
    effect(() => {
      this.getValuesBySearchSignal()
    }, {
        allowSignalWrites: true
    });
  }

Just be aware of the downsides of allowSignalWrites as stated in the documentation

Upvotes: 1

Related Questions