所以,我碰巧遇到了一个问题,从
DOM
中删除了错误的内容。在 onDelete()
方法中,我删除了指定索引处的 FormControl
。当我调试它时,TypeScript
删除了正确的值,但在DOM中,最后推送的值被删除了。
所以,我们说:
(删除按钮将从
formControl
中删除 formArray
并链接到 onDelete()
)
打字稿:
export class RecipeAddComponent implements OnInit, OnDestroy {
recipeForm!: FormGroup;
selectedFile?: File;
private editModeSub!: Subscription;
public isEditMode: boolean = false;
private recipeId?: number;
selectedFileUrl!: string | ArrayBuffer | null | undefined;
defaultImageSource: string =
'https://th.bing.com/th/id/OIP.ilYQWb6p_RYzTqdDqoTjqAHaHa?w=198&h=198&c=7&r=0&o=5&pid=1.7';
ngOnInit(): void {
this.initForm();
}
ngOnDestroy(): void {
this.editModeSub.unsubscribe();
}
constructor(
private readonly _recipeService: RecipeService,
private readonly _navigator: Router,
public dialog: Dialog,
private readonly _route: ActivatedRoute
) {}
get ingredientControls() {
return (this.recipeForm.get('ingredients') as FormArray).controls;
}
get stepControls() {
return (this.recipeForm.get('steps') as FormArray).controls;
}
get prepTimes() {
return (this.recipeForm.get('prepTimes') as FormArray).controls;
}
private initForm() {
//TODO this one probably needs refactor haha
let title = '';
let description = '';
let recipeImg: File;
let servings = '';
let servingsYield = '';
let ingredients;
let steps;
let prepTimes;
this.editModeSub = this._recipeService.bhSubject.subscribe((value) => {
this.recipeId = value.recipeId;
this.isEditMode = value.isEditMode;
this._recipeService.bhSubject.complete();
this._recipeService.bhSubject = new BehaviorSubject<{
isEditMode: boolean;
recipeId: number;
}>(null!);
});
if (!this.isEditMode) {
ingredients = new FormArray([
new FormControl('e.g. 2 spoons of sugar powder', Validators.required),
new FormControl('e.g. 1 cup of flour', Validators.required),
new FormControl('e.g. 2 eggs', Validators.required),
]);
steps = new FormArray([
new FormControl(
'e.g. Preheat oven to 350degrees C...',
Validators.required
),
new FormControl(
'e.g. Combine all dry ingredients in a large bowl..',
Validators.required
),
]);
prepTimes = new FormArray([
new FormGroup({
title: new FormControl('Prep Time', Validators.required),
time: new FormControl(0, Validators.required),
unit: new FormControl('', Validators.required),
}),
new FormGroup({
title: new FormControl('Cook Time', Validators.required),
time: new FormControl(0, Validators.required),
unit: new FormControl('', Validators.required),
}),
]);
} else {
ingredients = new FormArray<FormControl>([]);
steps = new FormArray([]);
prepTimes = new FormArray([]);
this._recipeService.fetchRecipe(this.recipeId).subscribe((recipe) => {
this.recipeForm.patchValue(recipe);
//TODO 3 fors just doesn't look right but it works now REFACTOR
for (const ingredient of recipe.ingredients) {
ingredients.push(new FormControl(ingredient, Validators.required));
}
for (const step of recipe.steps) {
steps.push(new FormControl(step, Validators.required));
}
for (const prepTime of recipe.prepTimes) {
prepTimes.push(
new FormGroup({
title: new FormControl(prepTime.title, Validators.required),
time: new FormControl(prepTime.time, Validators.required),
unit: new FormControl(prepTime.unit, Validators.required),
})
);
}
});
}
this.recipeForm = new FormGroup({
title: new FormControl(title, Validators.required),
description: new FormControl(description, Validators.required),
imageFile: new FormControl(recipeImg!),
servings: new FormControl(servings, Validators.required),
servingsYield: new FormControl(servingsYield),
ingredients: ingredients,
steps: steps,
prepTimes: prepTimes,
});
}
onAddTime() {
(this.recipeForm.get('prepTimes') as FormArray).push(
new FormGroup({
title: new FormControl(),
time: new FormControl(),
unit: new FormControl(),
})
);
}
// TODO suddenly this doesn't removeAt index but removes last in the DOM
onDelete(index: number, controlName: string) {
(this.recipeForm.get(controlName) as FormArray).removeAt(index);
}
onAdd(controlName: string, name: string) {
(this.recipeForm.get(controlName) as FormArray).push(
new FormControl('Add another ' + name, Validators.required)
);
}
HTML:
<div class="mx-auto p2 root-container">
<form [formGroup]="recipeForm" class="form-margin" (ngSubmit)="onSubmit()">
<div>
<p>some paragraph here</p>
</div>
<hr />
<div class="tdp-containter">
<div>
<div class="input-container">
<mat-form-field class="example-full-width">
<mat-label>Recipe title</mat-label>
<input
matInput
type="text"
placeholder="Give your recipe a title"
formControlName="title"
/>
</mat-form-field>
</div>
<div>
<mat-form-field class="example-full-width">
<mat-label>Description</mat-label>
<textarea
matInput
type="text"
placeholder="Share the story behind your recipe!"
formControlName="description"
></textarea>
</mat-form-field>
</div>
</div>
<div>
<input
type="file"
(change)="onFileSelected($event)"
class="file-input"
formControlName="imageFile"
#fileInput
/>
<button type="button" (click)="fileInput.click()">
<img
class="img-fluid"
[src]="selectedFileUrl ? selectedFileUrl : defaultImageSource"
alt="Selected Image"
/>
</button>
</div>
</div>
<hr />
<h5>Ingredietns</h5>
<p>
Enter one ingredient per line. Include the quantity (i.e. cups,
tablespoons) and any special preparation (i.e. sifted, softened, chopped).
Use optional headers to organize the different parts of the recipe (i.e.
Cake, Frosting, Dressing).
</p>
@for (item of ingredientControls; track $index) {
<div formArrayName="ingredients">
<div class="container-with-delete">
<mat-form-field class="example-full-width">
<mat-label>Ingredient</mat-label>
<input
matInput
type="text"
[placeholder]="item.value"
[formControlName]="$index"
/>
</mat-form-field>
<button type="button" (click)="onDelete($index, 'ingredients')">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
}
<button type="button" (click)="onAdd('ingredients', 'ingredient')">
Add Ingredient
</button>
<hr />
<h5>Directions</h5>
<p>
Explain how to make your recipe, including oven temperatures, baking or
cooking times, and pan sizes, etc. Use optional headers to organize the
different parts of the recipe (i.e. Prep, Bake, Decorate).
</p>
@for (item of stepControls; track $index) {
<div formArrayName="steps">
<div class="container-with-delete">
<mat-form-field class="example-full-width">
<mat-label>Step {{ $index + 1 }}</mat-label>
<textarea
matInput
type="text"
[placeholder]="item.value"
[formControlName]="$index"
></textarea>
</mat-form-field>
<button type="button" (click)="onDelete($index, 'steps')">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
}
<button type="button" (click)="onAdd('steps', 'step')">Add Step</button>
<hr />
<div class="container-with-delete">
<mat-form-field class="example-full-width">
<mat-label>Servings</mat-label>
<input
matInput
type="number"
min="0"
placeholder="e.g. 8"
formControlName="servings"
/>
</mat-form-field>
<mat-form-field class="example-full-width">
<mat-label>Yield (Optional)</mat-label>
<input
matInput
type="text"
placeholder="e.g. 1 9-inch cake"
formControlName="servingsYield"
/>
</mat-form-field>
</div>
<hr />
<div class="container-with-delete">
<div formArrayName="prepTimes">
@for (item of prepTimes; track $index) {
<div [formGroupName]="$index">
@if ($index < 2) {
<div class="container-with-delete">
<p>{{ item.get("title")?.value }}</p>
<mat-form-field>
<input
matInput
type="number"
min="0"
placeholder="0"
formControlName="time"
/>
</mat-form-field>
<mat-form-field>
<mat-label>Select</mat-label>
<mat-select formControlName="unit">
<mat-option value="mins">mins</mat-option>
<mat-option value="hours">hours</mat-option>
<mat-option value="days">days</mat-option>
</mat-select>
</mat-form-field>
</div>
} @else {
<mat-form-field>
<mat-label>Select</mat-label>
<mat-select formControlName="title">
<mat-option value="Freeze">Freeze</mat-option>
<mat-option value="Additional">Additional</mat-option>
<mat-option value="Cool">Cool</mat-option>
<mat-option value="Marinate">Marinate</mat-option>
<mat-option value="Rest">Rest</mat-option>
<mat-option value="Soak">Soak</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input
matInput
type="number"
min="0"
placeholder="0"
formControlName="time"
/>
</mat-form-field>
<mat-form-field>
<mat-label>Select</mat-label>
<mat-select formControlName="unit">
<mat-option value="mins">mins</mat-option>
<mat-option value="hours">hours</mat-option>
<mat-option value="days">days</mat-option>
</mat-select>
</mat-form-field>
<button type="button" (click)="onDelete($index, 'prepTimes')">
<mat-icon>delete</mat-icon>
</button>
}
</div>
}
<button type="button" (click)="onAddTime()">Add Time</button>
</div>
</div>
<hr />
<button [disabled]="recipeForm.invalid" type="submit">
{{ isEditMode ? "Save Changes" : "Submit Recipe" }}
</button>
</form>
</div>
尝试更改 @for 模板语法,即在模板上迭代成分形式数组的方式:
item
追踪,而不是 $index
index
,并将其发送到onDelete
方法而不是$index
试试这个:
@for (item of ingredientControls; track item; let index = $index;) {
<div formArrayName="ingredients">
<div class="container-with-delete">
<mat-form-field class="example-full-width">
<mat-label>Ingredient</mat-label>
<input
matInput
type="text"
[placeholder]="item.value"
[formControlName]="index"
/>
</mat-form-field>
<button type="button" (click)="onDelete(index, 'ingredients')">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
}
来源指南: