随着最近的Angular更新(我们正在使用v.17),我们开始在我们的应用程序中使用Angular信号.有一个问题是我们遇到的,我们不知道如何妥善处理.

我们的组件收到FormControl作为@Input.我们调整了代码以适应新的输入信号

public readonly control = input<FormControl | null>(null)

现在我们希望有valueChangesstatusChanges作为一个信号,但我们不知道如何.

这不是被动的:

public readonly value = toSignal(this.control().valueChanges);

这是不允许的:

public readonly value = computed(() => toSignal(this.control().valueChanges));

value作为一个信号可以帮助以更复杂的计算方式使用它.例如

public readonly complexCondition = computed(() =>  this.value() == // some complex checks

然后使用模板中的信号.

如果没有这一点,我们将需要一个getter或一个方法,这将不适用于OnPush-Strategy(我认为?)或者每个周期都会执行,导致不必要的调用,因为值可能只是保持不变.

还防止从效果手动设置信号.我们可以把它变成一个效果,设置一个基本变量,可能像这样:

effect(() => {
    this.control()?.valueChanges.subscribe(val => {
    this.complexCondition =  this.value() == // some complex checks
    })

});

但这看起来确实有点笨拙,我认为也不适合OnPush-战略

Question: 如何组织此类案件?

我试图在Angular文档或博客中找到任何东西,但我发现了类似的问题.我想我可能在错误的轨道上在这里,但期待看到正确的方式来做到这一点.

EDIT

我继续阅读这个主题,我认为真正能解决我的问题的是角形式提供信号.或者,我们必须将formControl包装在我们自己的类中,并提供自己的信号逻辑,但不使用toSignal(对于valueChanges和statusChanges很方便),因为这不能在注入上下文之外使用.

有这个开放功能请求:https://github.com/angular/angular/issues/53485

据我所知,这是Angular团队正在努力的事情.在那之前,人们可以使用这个包:https://github.com/timdeschryver/ng-signal-forms

这对我们来说不是一个选项,因为切换到这个包将意味着很多更改.但也许这对其他人有帮助.

推荐答案

关键考虑因素:

  1. 控件的值将发生变化,并且子控件应该适应控件状态的更新
  2. 总是会有一个定义的表单控件,如果你仍然想使用null作为初始值,那么@if是必要的,否则你可以放弃html中的if条件!

控件是一个输入属性,因此它可以在将来更新,所以需要考虑到这一点.

  1. 由于信号输入发生了变化,我使用effect来验证控件何时更新

  2. 然后我订阅了信号控件的valueChanges observable,但确保将其存储在一个subscription对象中,为什么我这样做是因为当控件改变时,前一个值改变的订阅是无用的,所以我们需要清理它,我使用了一个if条件和一个unsubscribe.

  3. 当控件的值发生变化时,我创建一个新的信号来存储控件的值,并设置值内的值变化

  4. 最后,我添加了一个清理块,因为订阅在组件销毁后不应该存在.

  5. 由于我们在效果中设置信号的值,所以我使用allowSignalWrites: true来允许这样做!

  6. 我使用一个计算信号来执行任何需要的复杂计算,这里它只是标签(valueFromComplexCondition)的切换

完整代码

子元素

import { Component, input, effect, computed, signal } from '@angular/core';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-test',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
  @if(control(); as controlObj) {
    <input [formControl]="controlObj"/>
    <br/>
    valueFromComplexCondition: {{valueFromComplexCondition()}}
  }
  `,
  styleUrl: './test.component.css',
})
export class TestComponent {
  public readonly control = input<FormControl>(new FormControl());
  private subscription: Subscription = new Subscription();
  valueFromComplexCondition = computed(() =>
    this.value() === 'hello!' ? 'Correct' : 'incorrect'
  );
  private value = signal(this.control().value);

  constructor() {
    effect(
      (onCleanup: Function) => {
        if (this.subscription) {
          this.subscription.unsubscribe();
        }
        const control = this.control();
        if (control) {
          this.value.set(control.value);
          this.subscription = control.valueChanges.subscribe((val) => {
            console.log(val);
            this.value.set(val);
          });
        }

        onCleanup(() => {
          this.subscription.unsubscribe();
        });
      },
      {
        allowSignalWrites: true,
      }
    );
  }
}

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { TestComponent } from './app/test/test.component';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TestComponent],
  template: `
  
    <app-test [control]="!switcher ? formControlA : formControlB"/>
    <button (click)="switcher = !switcher">toggle controls {{switcher ? "formControlB" : "formControlA"}}</button>
    <br/>
    formControlA: {{formControlA.value}}
    <br/>
    formControlB: {{formControlB.value}}
  `,
})
export class App {
  name = 'Angular';
  switcher = false;
  formControlA = new FormControl('hello!');
  formControlB = new FormControl('hello world!');
}

bootstrapApplication(App);

Stackblitz Demo

Angular相关问答推荐

Angular独立组件(使用LoadComponents)-懒惰加载服务不工作

布局之间的Angular 变化检测需要重新加载

Ionic 5角形共享两个模块之间的组件

带迭代的Angular 内容投影

向Anguar 17项目添加服务以启动和停止Loader-NGX-UI-Loader

如何使用解析器处理Angular 路由中存储的查询参数以避免任何额外的导航?

AOS中的数据绑定--可能吗?

带独立零部件的TranslateLoader[Angular 16]

如何在 Angular14 中创建带有验证的自定义输入组件?

检测父元素是否绑定到 Angular 中的子元素 @Output

如何处理Angular 延迟订阅

尽管模型中的变量发生了变化,但Angular 视图没有更新

如何在插值中编写条件?

类型错误:无法读取未定义的属性

如何将新的 FormGroup 或 FormControl 添加到表单

Angular 2在单击子项时防止单击父项

相对于根目录的 SCSS 导入

Angular 2 setinterval() 继续在其他组件上运行

从 angular2 模板调用静态函数

如何在 Angular 2 中跟踪路由?