7

Imagine standard situation in Angular: You need to fetch data from server for some entity ID. Entity Id is a Signal and you want the fetched data to be signal too.

Something like this feels natural:


@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule],
  template: `
  <code>{{roles()}}</code>
  `,
})
export class App {
  userId: Signal<string> = signal('userA');
  roles = computed(() => toSignal(getRoles(this.userId())));
}

//this could be api service sending  http request
const getRoles = (userId: string) => {
  return userId === 'userA' ? of([1, 2, 3]) : of([1]);
};

but there is a runtime errror in browser console:

Error: NG0203: toSignal() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`. Find more at https://angular.io/errors/NG0203

Stackblitz demo here

UPDATE: I also tried to provide Injector into toSignal:

 constructor(private injector: Injector) {}
  userId: Signal<string> = signal('userA');
  roles = computed(() =>
    toSignal(getRoles(this.userId()), { injector: this.injector })()
  );

but then another runtime error:

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.
George Knap
  • 843
  • 9
  • 30
  • The second error is misleading since there is no `allowSignalWrites` option for `computed()` method. I checked the source code, but I couldn't find anything suspicious. You should create a GitHub issue for this since it's a really common use case. Also, this is still in the Developer Preview so these issues are expected I guess. – NeNaD May 07 '23 at 14:41
  • I see you have posted this issue on the repo : https://github.com/angular/angular/issues/50192 – Matthieu Riegler May 07 '23 at 15:13
  • Even if this worked as you'd planned you'd end up with `Signal>` and then have to dereference it twice `roles()()`. – Simon_Weaver Jul 06 '23 at 18:05

2 Answers2

2

How about something more like this:

export class App {
  userId = signal('userA');
  roles = signal<number[]>([]);

  roleEffect = effect(() =>
    this.getRoles(this.userId()).subscribe(
       r = this.roles.set(r)
    )
  );
}
DeborahK
  • 57,520
  • 12
  • 104
  • 129
0

I'd say, you have to handle the signal as any async value :

roles = computed(() =>
  getRoles(this.userId())
);

And call the async pipe :

{{roles() | async }

What do you think about it ?


Edit: About the 2nd error message, you're writing a signal by creating it with toSignal() which also subscribes and sets the new values. (See the source code)

Matthieu Riegler
  • 31,918
  • 20
  • 95
  • 134
  • well, interesting idea. In that case I guess I'd prefer to have effect that will create an observable and assign it to class propery. Sounds simpler. – George Knap May 07 '23 at 14:28
  • I'm not sure that's the kind of use cases effect is for. – Matthieu Riegler May 07 '23 at 14:43
  • You don't really get any benefits from using Signals at all with this way. Also it wouldn't initiate the http call until the template tried to display the value - which seems wrong. – Simon_Weaver Jul 06 '23 at 18:01