简体   繁体   中英

How can I initialize a Reactive Angular2 form using an Observable?

My plan is to store the values of a form in my ngrx store to allow my users to navigate around the site and back to the form if they wish. The idea would be that the values of the form would repopulate from the store using an observable.

here is how I'm doing it currently:

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]
    })
  }

The store is given an initial value which I have seen passes through my selector function properly, but by the time my form is created, I don't think that value has returned. Therefore this.recipe is still undefined.

Is this the wrong approach, or can I somehow ensure that the observable is returned before creating the form?

Although adding another layer might seem more complicated, it is much easier to deal with observables by splitting the single component into two: a container component and a presentational component.

The container component deals only with observables and not with the presentation. The data from any observables is passed to the presentation component via @Input properties and the async pipe is used:

@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);
  }
}

The presentational component receives simple properties and does not have to deal with observables:

@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
    });
  }
}

The notion of using container and presentational components is a general Redux concept and is explained in Presentational and Container Components .

I can think of two options...

Option 1:

Use an *ngIf on the html that displays the form something like

<form *ngIf="this.recipe">...</form>

Option 2: Use the async pipe in your template and create your model like:

component

model: Observable<FormGroup>;    
...
this.model = store.select(recipeBuilderSelector)
    .startWith(someDefaultValue)
    .map((recipe: Recipe) => {
        return fb.group({
            foodName: [recipe.name],
            description: [recipe.description]
        })
    })

template

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

You would have to consider how to handle updates to the store and to the current model.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM