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?
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.
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.
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.
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:
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With