~$ git clone https://github.com/Pieter-hogent/recipeapp.git (or git pull) ~$ cd recipeapp ~/recipeapp$ npm install ~/recipeapp$ git checkout -b mybranch 08654da
src/app/recipe/recipe.module.ts
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
CommonModule,
HttpClientModule,
MaterialModule,
ReactiveFormsModule
], ...
src/app/recipe/add-recipe/add-recipe.component.ts
export class AddRecipeComponent implements OnInit {
public recipe: FormGroup;
ngOnInit() {
this.recipe = new FormGroup({
name: new FormControl('risotto')
})
}
};
src/app/add-recipe/add-recipe.component.html
<mat-card>
<mat-card-title>add recipe</mat-card-title>
<mat-card-content>
<form [formGroup]="recipe" (ngSubmit)='onSubmit()'>
<mat-form-field>
<input
matInput
aria-label="name"
placeholder="name"
type="text"
formControlName="name"
/>
</mat-form-field>
<button type='submit' mat-raised-button>
add recipe
</button>
</form>
</mat-card-content>
</mat-card>
REMOVE {{ recipe.value | json }}
src/app/recipe/add-recipe/add-recipe.component.ts
export class AddRecipeComponent implements OnInit {
@Output() public newRecipe = new EventEmitter<Recipe>();
private recipe: FormGroup;
// [ ... ]
onSubmit() {
this.newRecipe.emit(new Recipe(this.recipe.value.name));
}
};
7bec462
src/app/recipe/add-recipe/add-recipe.component.ts
export class AddRecipeComponent implements OnInit {
ngOnInit() {
this.recipe = new FormGroup({
name: new FormControl('risotto', Validators.required,
[Validators.required, Validators.minLength(2)], [ ... ])
})
}
}
src/app/recipe/add-recipe/add-recipe.component.html
<mat-card>
<mat-card-title>add recipe</mat-card-title>
<mat-card-content>
<form [formGroup]="recipe" (ngSubmit)="onSubmit()">
<mat-form-field>
<input
matInput
aria-label="name"
placeholder="name"
type="text"
formControlName="name"
required
#spy
/>
</mat-form-field>
<button type="submit" mat-raised-button [disabled]='!recipe.valid'>
add
</button>
</form>
</mat-card-content>
</mat-card>
REMOVE {{ recipe.value | json }}
{{ spy.className }}
af010eb
ng-valid | ng-invalid | does the content conform to the validators? |
ng-pristine | ng-dirty | have the contents changed? |
ng-untouched | ng-touched | has the user touched this? |
src/app/recipe/add-recipe/add-recipe.component.html
<mat-card>
<mat-card-title>add recipe</mat-card-title>
<mat-card-content>
<form [formGroup]="recipe" (ngSubmit)="onSubmit()">
<mat-form-field>
<input matInput
aria-label="name" placeholder="name"
type="text" formControlName="name"
/>
<mat-error
*ngIf="recipe.get('name')['errors']?.required &&
recipe.get('name').touched"
>
is required and needs at least 2 characters
{{ getErrorMessage(recipe.get('name')['errors']) }}
</mat-error>
<mat-error
*ngIf="recipe.get('name')['errors']?.minlength && recipe.get('name').touched"
>
needs at least {{ recipe.get('name')['errors'].minlength.requiredLength }} characters
</mat-error>
</mat-form-field>
<button type="submit" mat-raised-button [disabled]='!recipe.valid'>add</button>
</form>
</mat-card-content>
</mat-card>
REMOVE {{ recipe.get('name')['errors'] | json }}
src/app/recipe/add-recipe/add-recipe.component.ts
export class AddRecipeComponent implements OnInit {
// [ ... ]
getErrorMessage(errors: any): string {
if (errors.required) {
return 'is required';
} else if (errors.minlength) {
return `needs at least ${errors.minlength.requiredLength}
characters (got ${errors.minlength.actualLength})`;
}
}
8c8b4e9
src/app/recipe/add-recipe/add-recipe.component.ts
import { FormGroup, FormControlFormBuilder } from '@angular/forms';
export class AddRecipeComponent implements OnInit {
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.recipe = new FormGroupthis.fb.group({
name: new FormControl(this.fb.control(['risotto')]
})
}
}
src/app/recipe/add-recipe/add-recipe.component.ts
public readonly unitTypes = ['Liter', 'Gram', 'Tbsp', 'Pcs'];
ngOnInit() {
this.recipe = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
ingredients: this.fb.array([ this.createIngredients() ])
});
}
createIngredients(): FormGroup {
return this.fb.group({
amount: [''],
unit: [''],
name: ['', [Validators.required,
Validators.minLength(3)]]
});
}
src/app/recipe/add-recipe/add-recipe.component.html
<div formArrayName="ingredients"
*ngFor="let item of recipe.get('ingredients')['controls']; let i = index">
<div [formGroupName]="...i">
<mat-form-field>
<input matInput type="text" aria-label="ingredient amount"
placeholder="amount" formControlName="amount"/>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="unit" aria-label="ingredient unit"
formControlName="unit">
<mat-option *ngFor="let type of unitTypes" [value]="type">
{{ type }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="name" aria-label="ingredient name"
type="text" formControlName="name"/>
</mat-form-field>
</div>
</div>
src/app/recipe/add-recipe/add-recipe.component.ts
onSubmit() {
let ingredients = this.recipe.value.ingredients.map(Ingredient.fromJSON);
ingredients = ingredients.filter(ing => ing.name.length > 2);
this.newRecipe.emit(new Recipe(this.recipe.value.name, ingredients));
this.recipe = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
ingredients: this.fb.array([this.createIngredients()])
});
}
43acb73
src/app/recipe/add-recipe/add-recipe.component.ts
export class AddRecipeComponent implements OnInit {
get ingredients(): FormArray {
return <FormArray>this.recipe.get('ingredients');
}
ngOnInit() {
this.ingredients.valueChanges
.pipe(debounceTime(400), distinctUntilChanged())
.subscribe(ingList => {
const lastElement = ingList[ingList.length - 1];
if ( lastElement.name && lastElement.name.length > 2 ) {
this.ingredients.push(this.createIngredients());
}
});
}
src/app/add-recipe/add-recipe.component.ts
function validateIngredientName(control: FormGroup)
: { [key: string]: any } {
if (
control.get('amount').value.length >= 1 &&
control.get('name').value.length < 2
) {
return { amountNoName: true };
}
return null;
}
export class AddRecipeComponent implements OnInit {
createIngredients(): FormGroup {
return this.fb.group({
amount: [''],
unit: [''],
name: ['', [Validators.required, Validators.minLength(3)]]
},
{ validator: validateIngredientName }
);
}
getErrorMessage(errors: any): string {
// [ ... ]
if (errors.required) {
return 'is required';
} else if (errors.minlength) {
return `needs at least ${
errors.minlength.requiredLength
} characters (got ${errors.minlength.actualLength})`;
} else if (errors.amountNoName) {
return `if amount is set you must set a name`;
}
}
src/app/add-recipe/add-recipe.component.html
<div
formArrayName="ingredients"
*ngFor="let item of ingredients.controls; let i = index"
>
<div [formGroupName]="i">
<mat-form-field>
<input [...] formControlName="amount"
/></mat-form-field>
[ ... ]
</div>
<mat-error *ngIf="item.errors && item.get('name').touched">
{{ getErrorMessage(item.errors) }}
</mat-error>
</div>