【问题标题】:How can I initialize a Reactive Angular2 form using an Observable?如何使用 Observable 初始化 Reactive Angular2 表单?
【发布时间】:2017-07-09 02:47:05
【问题描述】:

我的计划是将表单的值存储在我的 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?

【问题讨论】:

    标签: angular typescript observable ngrx reactive-forms


    【解决方案1】:

    我能想到两个选择...

    选项 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>
    

    您必须考虑如何处理对商店和当前模型的更新。

    【讨论】:

    • 我无法让它以这种方式工作。 startsWith() does not exist on type Observable。该函数似乎只存在于字符串。
    • .startWith() 在第一次迭代中工作,但是当第二次(开发模式)运行时,它再次未定义。我通过删除 startWith() 并将我的选择器更改为:return _.cloneDeep(state.recipebuilder) || someDefaultValue; 让它工作
    【解决方案2】:

    虽然添加另一层可能看起来更复杂,但通过将单个组件分成两部分来处理可观察对象要容易得多:container 组件和 presentational 组件。

    容器组件只处理可观察对象而不处理表示。来自任何 observables 的数据通过@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 中进行了解释。

    【讨论】:

    • 我无法以这种方式为我工作。在调用 Selector 之前以某种方式构建了表单
    • 是的,我明白你的意思。我应该在构造函数中创建表单,并且仅在更改 @Input 时应用更改。我已经更新了答案。无论您决定采用哪种方式,我都鼓励您考虑将事物分成容器和展示组件,因为它确实让生活更轻松。
    • 任何批评或 shusson 的选项 2 答案?我可以在没有容器的情况下使用[formGroup]="recipe$ | async"
    • 您绝对可以在没有容器/表示分离的情况下让它工作,但我发现分离对于较大的应用程序很有用。由您决定在您的情况下是否值得。这只是需要注意的事情;几乎总是有多种方法可以完成某事。我将容器组件用于所有商店和服务交互,让表示组件处理简单的输入。顺便说一句,我简化了使用@Input setter 而不是OnChanges 的答案。
    • @cartant 你能在展示布局上使用 ChangeStrategy.onPush 和 @Input() 吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-01
    • 2017-08-29
    • 1970-01-01
    • 2019-12-18
    • 1970-01-01
    • 1970-01-01
    • 2017-04-30
    相关资源
    最近更新 更多