如何从信号和可观察对象创建计算信号?

huangapple go评论55阅读模式
英文:

How to create computed signal from signal and observable?

问题

想象在 Angular 中的标准情境:
需要从服务器获取某个实体 ID 的数据。实体 ID 是一个 Signal,你希望获取的数据也是一个 Signal。

这样的代码看起来很自然:

@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())));
}

// 这可能是发送 HTTP 请求的 API 服务
const getRoles = (userId: string) => {
  return userId === 'userA' ? of([1, 2, 3]) : of([1]);
};

但在浏览器控制台中会出现运行时错误:

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 上查看演示

更新:我也尝试将 Injector 提供给 toSignal

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

但接着会出现另一个运行时错误:

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.
英文:

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: &#39;my-app&#39;,
  standalone: true,
  imports: [CommonModule],
  template: `
  &lt;code&gt;{{roles()}}&lt;/code&gt;
  `,
})
export class App {
  userId: Signal&lt;string&gt; = signal(&#39;userA&#39;);
  roles = computed(() =&gt; toSignal(getRoles(this.userId())));
}

//this could be api service sending  http request
const getRoles = (userId: string) =&gt; {
  return userId === &#39;userA&#39; ? 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&lt;string&gt; = signal(&#39;userA&#39;);
  roles = computed(() =&gt;
    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.

答案1

得分: 2

以下是翻译好的内容:

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

  roleEffect = effect(() =>
    this.getRoles(this.userId()).subscribe(
       r => this.roles.set(r)
    )
  );
}
英文:

How about something more like this:

export class App {
  userId = signal(&#39;userA&#39;);
  roles = signal&lt;number[]&gt;([]);

  roleEffect = effect(() =&gt;
    this.getRoles(this.userId()).subscribe(
       r = this.roles.set(r)
    )
  );
}

答案2

得分: 1

以下是翻译好的部分:

对于“effect”,不需要,因为你想要的可以通过rxjs互操作函数来实现。

对于“roles”信号,必须将“userId”转换为一个使用toObservable的可观察对象。然后,将这个新可观察对象的值传递到switchMap操作符,以从服务中获取“roles”值。最后,将内部可观察流通过toSignal转换回信号。

在上面的示例中,提供了一个初始值。你可以省略它,但这会创建一个初始的未定义发射。

英文:

There is no need for effect, as what you want can be accomplished with the rxjs interop functions.

For the roles signal, the userId has to be converted to an observable with toObservable. Then value of this new observable is piped to a switchMap operator to get the roles value from a service. Finally, the inner observable stream is converted back to a signal by passing it to toSignal.

export class App {
  readonly userId = signal(&#39;userA&#39;);

  readonly roles = toSignal(toObservable(this.userId).pipe(
    switchMap(userId =&gt; getRoles(userId)),
  ), { initialValue: [] });
}

In the example above, an initial value is provided. You can omit that, but that will create an initial undefined emission.

答案3

得分: 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)

英文:

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

roles = computed(() =&gt;
  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)

huangapple
  • 本文由 发表于 2023年5月7日 20:54:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76194064.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定