【问题标题】:Angular - Reactive form valueChanges. Avoid nested subscriptionsAngular - 反应形式 valueChanges。避免嵌套订阅
【发布时间】:2020-11-30 12:38:32
【问题描述】:

问题

所以。我正在尝试使用 异步数据 预填充一个反应式表单,这将发生很大变化。 asyncData$ 从 AppComponent 馈送到处理表单的子组件 - FormComponent 中的 @input()

SubscribingdataForm$tap() 运算符中是 ONLY 我可以做到的valueChanges 工作并实际发出值,但嵌套订阅是不行的,所以我想找到一个更好/更清洁的解决方案来避免这种情况 - 最好是使用 observables。可能只是我遗漏了一些明显的东西

有人有建议吗?

表单本身必须是可观察的


应用组件

<div>
  <app-form [asyncData]="asyncData$ | async"></app-form>
</div>
export class AppComponent implements OnInit {
  title = 'observableForm';

  asyncData$: ReplaySubject<Data> = new ReplaySubject<Data>(1);

  ngOnInit(): void {
    setTimeout(() => this.asyncData$.next( {
      id: 42,
      name: 'Han Solo',
      heShotFirst: true
    } as Data), 2000);
  }
}

表单组件

<div *ngIf="(dataForm$ | async) as dataForm">
    <form [formGroup]="dataForm" style="padding: 5rem; display: grid; place-items: center;">
        <div>
            <label>
                Id
                <input type="text" [formControlName]="'id'">
            </label>
            <label>
                Name
                <input type="text" [formControlName]="'name'">
            </label>
            <br>
            <label>
                He shot first
                <input type="radio" [value]="true" [formControlName]="'heShotFirst'">
            </label>         
            <label>
                He did not
                <input type="radio" [value]="false" [formControlName]="'heShotFirst'">
            </label>         
        </div>
    </form>
    <div *ngIf="(lies$ | async) === false" style="display: grid; place-items: center;">
        <h1>I Must Not Tell Lies</h1>
    </div>
</div>
export class FormComponent implements OnInit, OnDestroy {
  @Input() set asyncData(data: Data) { this._asyncData$.next(data); }
  _asyncData$: ReplaySubject<Data> = new ReplaySubject<Data>(1);

  dataForm$: Observable<FormGroup>;
  valueChangesSub$: Subscription;

  lies$: Subject<boolean> = new Subject<boolean>();

  constructor(private fb: FormBuilder) { }

  ngOnInit(): void {
    this.dataForm$ = this._asyncData$.pipe(
      map(data => {
        return this.fb.group({
          id: [data?.id],
          name: [data?.name],
          heShotFirst: [data?.heShotFirst]
        });
      }),
      tap(form => {
            if (this.valueChangesSub$ != null) {
              this.valueChangesSub$.unsubscribe();
            }
            return form.valueChanges.subscribe(changes => {
              console.log(changes)
              this.lies$.next(changes.heShotFirst);
            });
          })
    );
  }

  ngOnDestroy(): void {
    this.valueChangesSub$.unsubscribe();
  }
}

【问题讨论】:

  • 是否可以用 Stackblitz 构建一个小的工作示例来说明它是如何不工作的?
  • 我很好奇为什么表单本身需要是可观察的?

标签: angular typescript rxjs reactive-programming angular-reactive-forms


【解决方案1】:

我不确定为什么表单需要成为 Observable。我改为使用OnChanges 来监视来自父组件的更改,并使用valueChanges 来监视表单的更改。

这是生成的代码:

import { Component, Input, OnInit, OnDestroy, OnChanges, SimpleChanges } from "@angular/core";
import { ReplaySubject, Observable, Subscription, Subject } from "rxjs";
import { Data } from "./data";
import { FormGroup, FormBuilder } from "@angular/forms";
import { map, tap } from "rxjs/operators";

@Component({
  selector: "app-form",
  templateUrl: "./form.component.html"
})
export class FormComponent implements OnInit, OnDestroy, OnChanges {
  @Input() asyncData: Data;

  dataForm$: Observable<FormGroup>;
  dataForm: FormGroup;
  valueChangesSub: Subscription;

  lies$: Subject<boolean> = new Subject<boolean>();

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.dataForm = this.fb.group({
      id: "",
      name: "",
      heShotFirst: false
    });

    this.valueChangesSub = this.dataForm.valueChanges.subscribe(changes => {
      console.log(changes);
      this.lies$.next(changes.heShotFirst);
    });
  }

  ngOnChanges(changes: SimpleChanges) {
    let currentValue: Data = changes.asyncData.currentValue;
    console.log("changes", currentValue);

    if (currentValue !== null) {
      this.dataForm.patchValue({
        id: currentValue.id,
        name: currentValue.name,
        heShotFirst: currentValue.heShotFirst
      });
    }
  }

  ngOnDestroy(): void {
    this.valueChangesSub.unsubscribe();
  }
}

我在这里有一个堆栈闪电:https://stackblitz.com/edit/angular-async-form-deborahk

使用OnChanges,每次将新值发送到父流中时,子组件都会收到通知。 (我试图通过在父组件中设置第二个setTimeout 来模拟它。)

然后表单上的valueChanges 似乎按预期工作。

如果这不是您想要的,请随时 fork stackblitz 并根据需要对其进行修改以演示您的问题。

【讨论】:

  • 表单本身不必是可观察的,它只是为了鼓励类似于我自己的解决方案。但这是 observables 的完美替代品,而且看起来更干净!
  • 足够回答你的问题了吗?如果是这样,请将其标记为您的答案以关闭问题。谢谢!
【解决方案2】:

设置两个 Observable 怎么样?

this.dataForm$ = this._asyncData$.pipe(
  map(data => this.fb.group({
    id: [data?.id],
    name: [data?.name],
    heShotFirst: [data?.heShotFirst]
  })),
);
this.lies$ = this.dataForm$.pipe(
  switchMap(form => form.valueChanges),
  map(changes => changes.heShotFirst),
);

【讨论】:

  • 这是我最初的方法,但由于某种原因 valueChanges 不喜欢被 switchMapped。或者合并或合并映射
猜你喜欢
  • 2022-08-25
  • 2023-01-30
  • 1970-01-01
  • 2018-11-27
  • 1970-01-01
  • 2020-08-18
  • 2020-05-31
  • 2019-10-03
  • 2023-01-04
相关资源
最近更新 更多