Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 426
Nate May Avatar asked Feb 19 '17 23:02

Nate May


People also ask

Is FormControl an observable?

Both FormControls and FormGroups expose an observable called valuesChanged . By subscribing to this observable we can react in real-time to changing values of an individual form control, or a group of form controls.

How do you validate a Reactive form?

To use an async validator in reactive forms, begin by injecting the validator into the constructor of the component class. Then, pass the validator function directly to the FormControl to apply it.

What command creates the Reactive form component?

To use reactive form controls, import ReactiveFormsModule from the @angular/forms package and add it to your NgModule's imports array. Use the CLI command ng generate to generate a component in your project to host the control.

What are reactive forms in Angular 2?

With reactive forms, you build your own representation of a form in the component class. Note: Reactive forms were introduced with Angular 2. In this article, you will explore how reactive forms can be applied to an example Angular application. If you would like to follow along with this article, you will need:

How do I use novalidate in Angular 4+?

With Angular 4+, novalidate is automatically added behind the scenes. formGroup: The form will be treated as a FormGroup in the component class, so the formGroup directive allows to give a name to the form group. ngSubmit: This is the event that will be triggered upon submission of the form.

What are the different types of forms in angular?

Angular provides two ways to work with forms: template-driven forms and reactive forms (also known as model-driven forms ). Template-driven forms are the default way to work with forms in Angular. With template-driven forms, template directives are used to build an internal representation of the form.

What is the difference between reactive and template driven forms in angular?

Template-driven forms are the default way to work with forms in Angular. With template-driven forms, template directives are used to build an internal representation of the form. With reactive forms, you build your own representation of a form in the component class.


Video Answer


2 Answers

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.

like image 62
cartant Avatar answered Oct 22 '22 23:10

cartant


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.

like image 31
shusson Avatar answered Oct 22 '22 21:10

shusson