It it recommeded that when deals with form component, we can create a container component to hold state, and then create a stateless component to enpower the form.
For example:
In the example has two components, one is container component 'meal.component.ts', another is statless component 'meal-form.component.ts'.
For the container component, it talks to service:
import {Component} from '@angular/core'; import {Meal} from '../../../shared/services/meals/meals.service'; @Component({ selector: 'meal', styleUrls: ['meal.component.scss'], template: ` <div class="meal"> <div class="meal__title"> <h1> <img src="/img/food.svg" alt="Food"> <span>Create meal</span> </h1> </div> <div> <meal-form (create)="addMeal($event)" ></meal-form> </div> </div> ` }) export class MealComponent { constructor() { } addMeal(meal: Meal) { console.log("meal", JSON.stringify(meal, null, 2)) } }
So 'addMeal' function will dispatch action to talk to the service.
For statless component:
import {ChangeDetectionStrategy, Component, EventEmitter, Output} from '@angular/core'; import {FormBuilder, FormArray, FormGroup, FormControl, Validators} from '@angular/forms'; import {Meal} from '../../../shared/services/meals/meals.service'; @Component({ selector: 'meal-form', changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['meal-form.component.scss'], template: ` <div class="meal-form"> <form [formGroup]="form"> <div class="meal-form__name"> <label> <h3>Meal name</h3> <input type="text" formControlName="name" placeholder="e.g. English Breakfast"> <div class="error" *ngIf="required"> Workout name is required </div> </label> </div> <div class="meal-form__food"> <div class="meal-form__subtitle"> <h3>Food</h3> <button type="button" (click)="addIngredient()" class="meal-form__add"> <img src="/img/add-white.svg" alt="Add food"> Add food </button> </div> <div formArrayName="ingredients"> <label *ngFor="let c of ingredients.controls; index as i;"> <input type="text" [formControlName]="i" placeholder="e.g Eggs"> <span class="meal-form__remove" (click)="removeIngredient(i)" ></span> </label> </div> </div> <div class="meal-form__submit"> <div> <button type="button" class="button" (click)="createMeal()"> Create Meal </button> <a [routerLink]="['../']" class="button button--cancel"> Cancel </a> </div> </div> </form> </div> ` }) export class MealFormComponent { @Output() create = new EventEmitter<Meal>(); form = this.fb.group({ name: ['', Validators.required], ingredients: this.fb.array(['']) }); get ingredients () { // Type check for ingredients, mark as FormArray // Therefore when we use 'ingredients', // We can get auto complete return this.form.get('ingredients') as FormArray; } get required() { return ( this.form.get('name').hasError('required') && this.form.get('name').touched ); } constructor(private fb: FormBuilder) { } createMeal() { if (this.form.valid) { this.create.emit(this.form.value); } } addIngredient() { // Add a new FormControl to FormArray this.ingredients.push(new FormControl('')); } removeIngredient(i: number) { this.ingredients.removeAt(i); } }
It uses ReactiveForm to create form.
Things to be notice:
1. Add type check for form array:
get ingredients () { // Type check for ingredients, mark as FormArray // Therefore when we use 'ingredients', // We can get auto complete return this.form.get('ingredients') as FormArray; }
Then whenever you use 'this.ingredients', it will show auto complete.
2. FormArray method:
addIngredient() { // Add a new FormControl to FormArray this.ingredients.push(new FormControl('')); } removeIngredient(i: number) { this.ingredients.removeAt(i); }