【问题标题】:Remove and add object item into array and change object key if specific item already exists in the array如果数组中已存在特定项,则删除并将对象项添加到数组中并更改对象键
【发布时间】:2020-09-24 19:10:50
【问题描述】:

我有这个食谱数组,其中每个对象都是一个特定的食谱,每个食谱都有一个配料数组,每个配料都是由_idnamequantity 组成的对象。如果你们知道为什么会发生这种情况,请在下面添加我的问题,请告诉我。我挣扎了 3 天......任何建议将不胜感激。非常感谢!

(3) [{…}, {…}, {…}]
0:
ingredients: Array(4)
0: {_id: "5f6628d0029e87e02c79ce0a", name: "chia", quantity: 10}
1: {_id: "5f6628d0029e87e02c79ce0b", name: "apple", quantity: 15}
2: {_id: "5f6628d0029e87e02c79ce0c", name: "honey", quantity: 30}
3: {_id: "5f6628d0029e87e02c79ce0d", name: "almond flour", quantity: 35}
length: 4
__proto__: Array(0)
name: "Coconut Chia Pudding"
__v: 0
_id: "5f6628d0029e87e02c79ce09"
__proto__: Object
1: {_id: "5f6628d0029e87e02c79ce0e", name: "Peanut Butter Cookies", ingredients: Array(4), __v: 0}
2: {_id: "5f6628d0029e87e02c79ce13", name: "Caprese Avocado Bowls", ingredients: Array(3), __v: 0}
length: 3
__proto__: Array(0)

我在 UI 中有一个包含上述食谱的列表,用户可以勾选和取消勾选,并且在勾选食谱后,其成分会显示在列表中。

HTML

<ion-content>
  <ion-grid>
    <ion-row>
      <ion-col>
        <ion-list>
          <ion-item
            *ngFor="let recipe of loadedRecipes; let lastRecipe = last"
            [ngClass]="{ 'last-recipe': lastRecipe }"
          >
            <ion-checkbox
              (ionChange)="onCheckRecipe($event)"
              value="{{recipe.name}}"
            ></ion-checkbox>
            <ion-label>{{recipe.name}}</ion-label>
            <ion-button
              [routerLink]="['/','recipes','recipe-details', recipe._id]"
              >></ion-button
            >
          </ion-item>
        </ion-list>
      </ion-col>
    </ion-row>

    <ion-row>
      <ion-col>
        <h6 class="ion-padding-start" *ngIf="groceryList.length > 0">
          Grocery List
        </h6>
        <ion-list *ngIf="groceryList.length > 0">
          <ion-item *ngFor="let ingredient of groceryList">
            <ion-label>{{ingredient.name}}</ion-label>
            <ion-note slot="end">{{ingredient.quantity}} g</ion-note>
          </ion-item>
        </ion-list>
      </ion-col>
    </ion-row>
  </ion-grid>
</ion-content>

我想要实现的(我已经在下面完成了,但我有一个错误)是当用户勾选要添加到名为 groceryList 的数组中的配方时,以及取消勾选配方以删除我的groceryList 数组中的成分。此外,如果我勾选了 1 个食谱,并且如果我要勾选的下一个食谱与之前勾选的成分相同,则只需增加已经存在的常见成分数量,而不是添加它两次,如果我想取消一个食谱以去除不常见的成分并减去常见成分的数量。我已经设法做到了,但是我有一个大问题,我不知道从哪里来。在 UI 中的某个时刻,如果我勾选和取消勾选食谱,并且我一个接一个地勾选具有相同成分的食谱,它会删除常见的成分即使我仍然有一个带有该成分的勾选食谱。同样,如果你们知道为什么会发生这种情况,请告诉我,我将不胜感激您的任何建议

我的 TS

import { Component, OnInit } from "@angular/core";
import { Subscription } from "rxjs";
import { RecipesService } from "src/app/services/recipes.service";

@Component({
  selector: "app-recipes",
  templateUrl: "./recipes.page.html",
  styleUrls: ["./recipes.page.scss"],
})
export class RecipesPage implements OnInit {
  loadedRecipes: any;
  private _recipesSub: Subscription;

  constructor(private recipesService: RecipesService) {}
  groceryList = [];

  ngOnInit() {
    this._recipesSub = this.recipesService.recipes.subscribe((receivedData) => {
      this.loadedRecipes = receivedData;
    });
  }

  onCheckRecipe(e) {
    if (e.detail.checked === true) {
      for (let recipe of this.loadedRecipes) {
        console.log(this.loadedRecipes);
        if (recipe.name === e.detail.value) {
          for (let eachIngredient of recipe.ingredients) {
            let matchedIng = this.groceryList.find(function (foundIng) {
              return foundIng.name === eachIngredient.name;
            });
            if (matchedIng) {
              matchedIng.quantity =
                matchedIng.quantity + eachIngredient.quantity;
            } else {
              this.groceryList.push(eachIngredient);
            }
          }
        }
      }
    } else {
      for (let recipe of this.loadedRecipes) {
        if (recipe.name === e.detail.value) {
          for (let eachIngredient of recipe.ingredients) {
            let matched = this.groceryList.find(function (foundIngre) {
              return foundIngre.name === eachIngredient.name;
            });
            if (
              matched.name === eachIngredient.name &&
              matched._id === eachIngredient._id
            ) {
              let index = this.groceryList.findIndex(
                (x) => x._id === matched._id
              );
              this.groceryList.splice(index, 1);
            } else {
              matched.quantity = matched.quantity - eachIngredient.quantity;
            }
          }
        }
      }
    }
  }

  ionViewWillEnter() {
    this.recipesService.fetchRecipes().subscribe();
  }

  ngOnDestroy() {
    if (this._recipesSub) this._recipesSub.unsubscribe();
  }
}

【问题讨论】:

    标签: javascript arrays angular typescript object


    【解决方案1】:

    问题在于您的 if 语句的流程。在代码的“onRemove”部分中,您说“如果成分在列表中,则将其从列表中删除。如果没有,则减少其数量。”第二部分没有任何意义,更重要的是,您永远无法达到它,因为成分应该始终在列表中。

    for (let eachIngredient of recipe.ingredients) {
      let matched = this.groceryList.find(function(foundIngre) {
        return foundIngre.name === eachIngredient.name;
      });
      if (
        matched.name === eachIngredient.name &&
        matched._id === eachIngredient._id
      ) {
        let index = this.groceryList.findIndex(
          (x) => x._id === matched._id
        );
        // Problem e ca eachIngredient.quantity se schimba
        this.groceryList.splice(index, 1);
      } else {
        matched.quantity = matched.quantity - eachIngredient.quantity;
      }
    }

    根据你所说的,你想做的是:

    1. 减去归因于已删除配方的数量
    2. 如果新数量为零,则从列表中删除该成分(尽管您也可以将其保留并忽略数量为零的成分)

    试试这个:

    for (let eachIngredient of recipe.ingredients) {
      // I am assuming that ids are unique so I am not checking foundIngre.name at all, 
      // since I assume that ingredients with the same name must also have the same name
      // I am also using findIndex first so that you don't need a second find when removing
      const matchIndex = this.groceryList.findIndex( 
         (foundIngre) => foundIngre._id === eachIngredient._id
      );
      if ( matchIndex ) { // this should always be true
        const matched = this.groceryList[matchIndex];
        // preserve the entry if there is still some quantity
        if ( matched.quantity > eachIngredient.quantity ) {
          matched.quantity = matched.quantity - eachIngredient.quantity; // can use -= to shorten
        }
        // remove from the list only if there is no quantity remaining
        else {
            this.groceryList.splice(matchIndex, 1);
        }
      }
    }

    编辑: 尝试更新和删除数组中的项目是不必要的痛苦。重新编写的代码版本将 _groceryList 存储在键控字典中。我最初打算按成分 ID 键入,但在查看您的演示后,我发现我的假设是不正确的,即多个食谱中的相同成分将共享相同的 ID。因此,我改为按成分名称键入。通过这种方式,您可以写入 _groceryList[name] 并且它以前是否存在都没有关系。

    该类有一个公共的 getter 杂货清单,它将私有的 _groceryList 字典转换为一个数组。

    我还尝试通过使用一个通用的toggleIngredient 函数来消除场景分支中不必要的代码重复,该函数使用布尔值checked 来控制它是通过乘以加一还是减一来控制它是加法还是减法。

    import { Component } from "@angular/core";
    import { Platform } from "@ionic/angular";
    import { SplashScreen } from "@ionic-native/splash-screen/ngx";
    import { StatusBar } from "@ionic-native/status-bar/ngx";
    import { Subscription } from "rxjs";
    
    export interface Ingredient {
      _id: string;
      name: string;
      quantity: number;
    }
    
    export interface Recipe {
      _id: string;
      name: string;
      ingredients: Ingredient[];
    }
    
    @Component({
      selector: "app-root",
      templateUrl: "app.component.html"
    })
    export class AppComponent {
    
      private _recipesSub: Subscription;
      constructor(
        private platform: Platform,
        private splashScreen: SplashScreen,
        private statusBar: StatusBar,
      ) {
        this.initializeApp();
      }
    
      initializeApp() {
        this.platform.ready().then(() => {
          this.statusBar.styleDefault();
          this.splashScreen.hide();
        });
      }
      private loadedRecipes: Recipe[] = [/*...*/]
    
      // store the groceryList in a dictionary keyed by name
      private _groceryList: Record<string, Ingredient> = {};
    
      // getter returns the groceryList in array format, ignoring 0 quantities
      get groceryList(): Ingredient[] {
        return Object.values(this._groceryList).filter( ing => ing.quantity > 0 );
      }
    
      // get the current quantity for an ingredient by name, or 0 if not listed
      currentQuantity( name: string ): number {
        const ingredient = this._groceryList[name];
        return ingredient ? ingredient.quantity : 0;
      }
    
      // update the quantity for an ingredient when checked or unchecked
      // will add new ingredients, but never removes old ones
      toggleIngredient( ingredient: Ingredient, checked: boolean ): void {
        // add to or remove from quantity depending on the value of checked
        const quantity = this.currentQuantity(ingredient.name) + (checked ? 1 : -1 ) * ingredient.quantity;
        // replace the object in the grocery list dictionary
        this._groceryList[ingredient.name] = {
          ...ingredient,
          quantity
        }
      }
    
      onCheckRecipe(e) { // you'll want to add a type for e here
        for (let recipe of this.loadedRecipes) {
          // find the matching recipe
            if (recipe.name === e.detail.value) {
              // loop through the recipe ingredients
              for (let eachIngredient of recipe.ingredients) {
                this.toggleIngredient(eachIngredient, e.detail.checked)
              }
            }
          }
      }
    }
    

    【讨论】:

    • 嗨!感谢你的回复!我刚刚尝试了您的选择,但是当我找到具有匹配成分的食谱并取消选中它时,我进入控制台“无法读取未定义的属性'数量'”,因此我尝试了类似的方法 if (matchIndex !== -1) {...} 而不是错误,但现在我得到的行为是,例如,我勾选所有食谱,我取消勾选具有共同成分的一个,它保留了该共同成分,但它不会减少数量。你知道为什么会这样吗?
    • 嗯,我真的不知道。可能是this.groceryList[matchIndex] 正在访问一个无效的索引,但我认为这是用if ( matchIndex ) {} 处理的。我将更多地使用代码。将 onCheckRecipe 分解成一堆更小的部分有助于清晰。你能分享一个你的演示链接吗?
    • 非常感谢!这也是我正在尝试做的......这是我的演示 stackblitz.com/edit/ionic-v4-chnobn?file=src/app/… 请让我知道它是否适合你
    • 有很多单独的场景需要处理(结合检查的真/假和成分在数组中的真/假)。如果可以,您希望更喜欢抽象而不是通过“if”语句进行分支。在您的原始代码中,您最终在 onChecked 和 onUnchecked 情况下出现了很多重复的行,因为最终它们在做同样的事情,只有微小的差异。您希望使差异尽可能小,以便可以共享所有其余代码。在我的版本中,这种差异减少到checked ? 1 : -1 ,加法与减法。
    【解决方案2】:

    我认为问题似乎出在这部分代码

    if (
      matched.name === eachIngredient.name &&
      matched._id === eachIngredient._id
    ) {
      let index = this.groceryList.findIndex(
        (x) => x._id === matched._id
      );
      // Problem e ca eachIngredient.quantity se schimba
      this.groceryList.splice(index, 1);
    } else {
      matched.quantity = matched.quantity - eachIngredient.quantity;
    }
    
    1. if 语句应该检查数量而不是再次验证名称和 id,类似于

      if(matched.quantity

    2. 寻找匹配成分的小建议。先使用 findIndex() 获取matchedIndex,再使用grocerylist[matchedIndex] 获取item,避免再次遍历grocerylist寻找拼接索引。

    【讨论】:

    • 嗨!感谢你的回复!我已经尝试了您的第 1 点,但不幸的是仍然存在相同的行为,关于第 2 点非常感谢您的建议,我会相应地进行这些更改
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 1970-01-01
    • 2017-03-04
    相关资源
    最近更新 更多