将 Angular 信号值从组件输入传递到服务

问题描述 投票:0回答:1

我正在使用 Angular 17 和信号进行一些 POC 工作,并遇到了一个用例,我不确定如何在不涉及

ngOnChanges
@Input
处理的情况下解决该问题。

因此,假设您有一个带有输入信号的组件。然后,您希望将这些相同的值复制/设置到服务中,以便该层次结构中的组件可以访问它。

我尝试将可写的

signal
放入服务中,但技巧是尝试在正确的时机从组件输入中设置服务值。因此,我可以使用组件初始信号值将值设置到服务中,但随后它们不会跟踪更改。

所以这似乎是

toObservable
effect
的不错用途,但听起来
toObservable
使用效果。似乎发生的情况是,效果在子组件初始化后运行(
ngOnInit
被调用)。所以,我的问题是如何监听信号值的变化,能够将它们设置在其他东西(提供商/服务)上而不传递实际信号,这似乎不是一个好主意。

仅使用“正常”组件,使用

ngOnChanges
可以轻松跟踪更改,但我想知道如何使用具有相同时序的信号来跟踪它。

大大简化的示例,但显示了问题

@Injectable()
export class ExampleProvider {
  readonly name = signal('');
}

@Component({ 
  ...
  providers: [ExampleProvider]
})
export class ExampleComponent {
  readonly name = input<string>();
  private readonly exampleProvider = inject(ExampleProvider);

  constructor() {
    effect(() => {
      // This will run after `ngOnInit` of child components
      this.exampleProvider.name.set(this.name());
    });
  }
}

如何将组件输入的更改传播到服务并在子组件初始化/重新渲染之前对其进行设置?

https://stackblitz.com/edit/stackblitz-starters-hjmcgz?file=src%2Fmain.ts

您可以运行这个示例。 有 4 个输入。虽然 UI 将显示正确的值,但如果您打开控制台,您会注意到在子组件的

ngOnInit
期间,服务尚未更新为
effect
toObservable
设置的值。

您将看到在输入更改期间,将运行两个更改检测。您将看到第一个具有旧值,然后第二个将具有更新值。

此外,在第一次渲染期间,您将看到 2 和 4 的值尚未设置:

top-level.component.ts:66 TopLevelComponent ngOnInit
  input1: a
  input2: b
  input3: c
  input4: d
child.component.ts:39 ChildComponent ngOnInit
  input1: a
  input2: 
  input3: c
  input4: 
angular signals
1个回答
0
投票

答案是不要为此使用效果。 Angular 文档指出

避免使用效果来传播状态变化。这可能会导致 ExpressionChangedAfterItHasBeenChecked 错误、无限循环更新或不必要的更改检测周期。

由于这些风险,Angular 默认情况下会阻止您设置效果信号。如果绝对必要,可以通过在创建效果时设置 allowedSignalWrites 标志来启用它。

任何时候您发现自己在常规应用程序代码中使用

allowSignalWrites: true
,通常都有更好的方法。

一种解决方案是重复传递输入,直到它们到达所有需要它们的组件。如果这是不可能或不可取的,那么通常解决方案将是依赖项注入,但具体如何执行取决于您的需求。

您可以让设置输入的组件在服务上设置值,而不是将输入连接到服务。例如,在这里我删除了输入,将 ExampleService 上移到根组件,并将服务的属性直接连接到 ngModels (StackBlitz):

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <div><label>A: <input [(ngModel)]="x.prop1"></label></div>
    <div><label>B: <input [(ngModel)]="x.prop2"></label></div>
    <div><label>C: <input [(ngModel)]="x.prop3"></label></div>
    <div><label>D: <input [(ngModel)]="x.prop4"></label></div>
    <top-level />
  `,
  imports: [TopLevelComponent, FormsModule],
  providers: [ExampleService],
})
export class App {
  x = inject(ExampleService);
}

如果您愿意,您也可以更改应用程序组件来实现ExampleService:

@Component({
  // ...
  providers: [{ provide: ExampleService, useExisting: App }],
})
export class App implements ExampleService {
  prop1 = signal('a');
  prop2 = signal('b');
  prop3 = signal('c');
  prop4 = signal('d');
}

不过,这可能会对您的应用程序造成相当大的破坏。如果您仍然希望在组件层次结构的较低位置提供ExampleService,则可以让您的TopLevelComponent实现ExampleService,或者您可以使用工厂提供程序将TopLevelComponent的输入映射到ExampleService,如下所示(StackBlitz):

@Directive({
  selector: '[example]',
  standalone: true,
  providers: [{
    provide: ExampleService,
    useFactory: (): ExampleService => {
      const c = inject(TopLevelComponent);
      // or new ExampleService(c.input1, ...) if you prefer that
      return { prop1: c.input1, prop2: c.input2, prop3: c.input3, prop4: c.input4 };
    },
  }],
})
export class ExampleServiceDirective {}

您可能会使用这些模式的多种变体,具体如何实现应取决于您使用ExampleService 到底做了什么。

© www.soinside.com 2019 - 2024. All rights reserved.