如何使用Observable初始化反应式Angular2表单?

13

我的计划是将表单的值存储在ngrx存储中,以允许用户在需要时浏览站点并返回表单。思路是使用可观察对象从存储中重新填充表单的值。

以下是我目前的做法:

constructor(private store: Store<AppState>, private fb: FormBuilder) {
    this.images = images;
    this.recipe$ = store.select(recipeBuilderSelector);
    this.recipe$.subscribe(recipe => this.recipe = recipe); // console.log() => undefined
    this.recipeForm = fb.group({
      foodName: [this.recipe.name], // also tried with an OR: ( this.recipe.name || '')
      description: [this.recipe.description]
    })
  }

这个店铺被赋予了一个初始值,我已经看到它通过我的选择器函数正确地传递了,但在创建表单时,我不认为该值已经返回。因此,this.recipe仍然是未定义的。

这种方法是错误的吗?还是我可以以某种方式确保在创建表单之前返回observable?

2个回答

12

尽管添加另一层可能会看起来更加复杂,但是通过将单个组件分成两个组件:一个容器组件和一个展示组件,可以更轻松地处理可观察对象。

容器组件仅处理可观察对象而不涉及呈现。来自任何可观察对象的数据都通过@Input属性传递给展示组件,并使用async管道:

@Component({
  selector: "recipe-container",
  template: `<recipe-component [recipe]="recipe$ | async"></recipe-component>`
})
export class RecipeContainer {

  public recipe$: Observable<any>;

  constructor(private store: Store<AppState>) {
    this.recipe$ = store.select(recipeBuilderSelector);
  }
}

表现组件接收简单的属性,不需要处理可观察对象:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "recipe-component",
  template: `...`
})
export class RecipeComponent {

  public recipeForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.recipeForm = this.formBuilder.group({
      foodName: [""],
      description: [""]
    });
  }

  @Input() set recipe(value: any) {
    this.recipeForm.patchValue({
      foodName: value.name,
      description: value.description
    });
  }
}

使用容器组件和展示组件的概念是Redux中的一个通用概念,详细解释可参考《Presentational and Container Components》


是的,我明白您的观点。我应该在构造函数中创建表单,并只在 @Input 改变时应用更改。我已经更新了答案。无论您决定采取哪种方式,我都鼓励您将事物分成容器和表示组件,因为这确实会使生活更轻松。 - cartant
有任何批评或Shusson的第二个选项答案吗?我能够通过使用[formGroup] =“recipe $ | async”而不使用容器来完成它。 - Nate May
2
你可以在没有容器/表示分离的情况下确保其正常运行,但我发现在更大的应用程序中进行分离会带来好处。这取决于您是否认为在您的情况下值得这样做。这只是需要注意的一点;通常有多种方法可以完成某件事。我使用容器组件处理所有存储和服务交互,使表示组件处理简单的输入。顺便说一句,我简化了答案,使用了一个 @Input setter 而不是 OnChanges - cartant
@cartant,你能否在表示层使用ChangeStrategy.onPush和@Input()? - rotemx
@cartant ~ 这个答案的方法真的很干净。 - P.M
显示剩余2条评论

6
我可以想到两个选项...
选项1:
在显示表单的HTML上使用*ngIf,类似于:
<form *ngIf="this.recipe">...</form>

选项2: 在模板中使用async管道,并创建以下模型:
组件
model: Observable<FormGroup>;    
...
this.model = store.select(recipeBuilderSelector)
    .startWith(someDefaultValue)
    .map((recipe: Recipe) => {
        return fb.group({
            foodName: [recipe.name],
            description: [recipe.description]
        })
    })

模板

<app-my-form [model]="(model | async)"></app-my-form>

您需要考虑如何处理商店和当前模型的更新。


1
我无法用这种方式使它工作。 “startsWith()在类型Observable上不存在”。该函数似乎仅适用于字符串。 - Nate May
抱歉,方法应该是 startWith。https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/startwith.md - shusson
.startWith() worked on the first iteration, but when the second (dev mode) ran it was again undefined. I got it to work by removing startWith() and changing my selector to: return _.cloneDeep(state.recipebuilder) || someDefaultValue; - Nate May

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接