Angular FORM,从 DOM 中删除不会在指定索引处删除,但会删除最后一项

问题描述 投票:0回答:1

所以,我碰巧遇到了一个问题,从

DOM
中删除了错误的内容。在
onDelete()
方法中,我删除了指定索引处的
FormControl
。当我调试它时,
TypeScript
删除了正确的值,但在DOM中,最后推送的值被删除了。

所以,我们说:

(删除按钮将从

formControl
中删除
formArray
并链接到
onDelete()

  1. [某输入框][删除按钮]<- when I press this button
  2. [其他一些输入框][删除按钮]
  3. [其他一些输入框][删除按钮]<- this gets removed even when in TypeScript the first value gets removed.

打字稿:

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>

javascript angular typescript forms dom
1个回答
0
投票

尝试更改 @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>
    }

来源指南:

Angular @for:完整指南

© www.soinside.com 2019 - 2024. All rights reserved.