0

I'm stuck with a problem, and I cannot solve it in TypeScript. I'm using Angular, this code is from a service. I have this array of ingredients:

private ingredients: Ingredient[] = [
  new Ingredient('farina', 500),
  new Ingredient('burro', 80),
  new Ingredient('uccellini', 5)
];

This is the model: export class Ingredient {constructor(public name: string, public amount: number){}}

Now I want to add new ingredients to the array, and emit an event with a copy of the new array: this works:

newIngredients = [
  new Ingredient('mais', 100),
  new Ingredient('uccellini', 5)
];

addIngredients(newIngredients: Ingredient[]) {
  this.ingredients.push(...ingredients);
  this.ingredientsChanged.emit(this.ingredients.slice());
}

But I want to check if a new ingredient object exists in the ingredients array, and if it exists, sum the old and the new amount value in a single object, and push the updated object, then return a copy of the array.

Expected output:

[
  new Ingredient('farina', 500),
  new Ingredient('burro', 80),
  new Ingredient('uccellini', 10)
  new Ingredient('mais', 100)
];

I try with Set, WeakSet, Map and other way, but I do not know Typescript so much. This is where I'm stuck:

addIngredients(newIngredients: Ingredient[]) {

  let hash = {};
  this.ingredients.forEach(function (ingr) {
    hash[ingr.name] = ingr;
  });

let result = newIngredients.filter(function (ingr) {
  if (!(ingr.name in hash)) {
    return !(ingr.name in hash);
  } else {
    // ???
  }
});

  this.ingredients.push(...result);
  this.ingredientsChanged.emit(this.ingredients.slice());
}

Thanks for help

3 Answers 3

2

let say you have two arrays

const ingredients: Ingredient[] = [
  new Ingredient('farina', 500),
  new Ingredient('burro', 80),
  new Ingredient('uccellini', 5)
];

and

const newIngredients = [
  new Ingredient('mais', 100),
  new Ingredient('uccellini', 5)
];

and you want to combine them if adding ingredients that does not exist and combining the one that exist,

what you can do is combine both arrays and then reduce the until have only one list

const combined = ingredients
  .concat(newIngredients)
  .reduce((prev: any[], curr: any) => {
    const temp = prev.filter(a => a.name === curr.name)
    if (temp.length > 0) {
      prev.push({ ...curr, value: temp.shift().value + curr.value })
    } else {
      prev.push(curr)
    }
    return prev
  }, [])
Sign up to request clarification or add additional context in comments.

2 Comments

sorry, but does not work, combined have a new value but not of type Ingredient, and is an object with {name: "uccellini", amount: 5, value: NaN}. However, I think that a combination of .concat() and .reduce() can helps. Thank you
my code is just a example of how you can solve your problem, is not real data,
1

Updated answer:

  1. 'this.ingredients' is not accessible in the forEach, because context for that function call has changed

  2. Hence assigning 'this' to 'that' variable, which enables 'that.ingredients' will be accessible inside forEach

StackBlitz url: https://stackblitz.com/edit/angular-fulrcg

addIngredients(newIngredients: Ingredient[]) {

    let exist: boolean = false;
    let that = this;
    //debugger;
    console.log(this.ingredients);
    newIngredients.forEach(function(newIngr, newKey) {
      exist = false;
      console.log(newIngr);
      that.ingredients.forEach(function (ingr, key) {
        if(ingr["name"] == newIngr["name"]) {
          ingr["amount"] += newIngr["amount"];
          exist = true;
        }
      });
      if(!exist) {
        **that.ingredients**.push(newIngr);
      }
    });

1 Comment

PS: using arrow function I do not need to define that = this
1

Following are the code snippet:

Stackblitz URL: https://stackblitz.com/edit/angular-uf4dcr

app.component.html

<button (click)="add()">Add</button>

<ul>
  <li *ngFor="let ing of ingredients">
    {{ing.name}} - {{ing.amount}}
  </li>
</ul>

app.component.ts

import { Component } from '@angular/core';

class Ingredient {
  constructor(public name: string, public amount: number){ }
}



@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular 5';

  private ingredients: Ingredient[] = [
    new Ingredient('farina', 500),
    new Ingredient('burro', 80),
    new Ingredient('uccellini', 5)
  ];

  addIngredients(newIngredients: Ingredient) {

    let exist: boolean = false;
    this.ingredients.forEach(function (ingr) {
      if(ingr["name"] == newIngredients["name"]) {
        ingr["amount"] += newIngredients["amount"];
        exist = true;
      }
    });

    if(!exist) {
      this.ingredients.push(newIngredients);
    }

      //this.ingredientsChanged.emit(this.ingredients.slice());
      console.log([...this.ingredients]); //return copy of the updated array
  }

  add() {
    this.addIngredients({name: 'farina', amount:10});
    this.addIngredients({name: 'new item', amount:100});
  }

}

2 Comments

sorry, but does not work if pass an array of Ingredient typed object, only works if you pass a single object
Added new answer. Problem is context inside forEach. this.ingredients is not accessible inside forEach. Ref: stackoverflow.com/questions/15013016/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.