2

I'm very new to all of this so please take it easy on me, I've searched for everything that I can think of searching for but I just can't figure this out.

I am currently building an ionic3 app and have shopping lists set up so that users can add a recipe to a shopping list - which works fine - but I want to tweak it so that if multiple of the same item are added, instead of having two instances of the same item, it updates the first item to show that it has new data. I can send the items through a service and retrieve them on another page, and I can sort them alphabetically, but I can't get the merging function to work.

Thank you very much in advance for your help.

Here is the (hopefully) relevant code:

import { Injectable } from "@angular/core";
import { Storage } from "@ionic/storage";
import { SLIngredient } from "../data/shopping-interface";

@Injectable()
export class ShoppingService {
  storedDay: SLIngredient[] = []
  storedWeek: SLIngredient[]
  userSL: SLIngredient[] = []
  sortedList: SLIngredient[] = []
  mergedItem: SLIngredient;
  mergedList: SLIngredient[] = [];

  constructor(private storage: Storage) {}

  getShoppingDay(day: number) {
    return this.storage.get('shoppingDay/' + day)
      .then(
        (storedDay: SLIngredient[]) => {
          this.storedDay = storedDay != null ? storedDay : [];
          return this.storedDay.slice();
        }
      )
  }

  getShoppingWeek(id: number) {
    return this.storage.get('shoppingWeek/' + id)
      .then(
        (storedWeek: SLIngredient[]) => {
          this.storedWeek = storedWeek != null ? storedWeek : [];
          return this.storedWeek.slice();
        }
      )
  }

  addItem(name: string, amount: number, measurement: string, completed: boolean) {
    this.userSL.push(new SLIngredient(name, amount, measurement, completed));
  }

  addItems(items: SLIngredient[]) {
    this.userSL.push(...items);
    this.sort()
    this.setUserSL();
    console.log(this.userSL)
  }

  getItems() {
    this.getUserSL;
    return this.userSL.slice();
  }

  sort() {
    this.sortedList = this.userSL.sort((itemA: SLIngredient, itemB: SLIngredient) => {
      const a = itemA.name.toLowerCase()
      const b = itemB.name.toLowerCase()

      if (a > b) return 1;
      if (a < b) return -1;
      if (a == b) {
        this.merge(itemA, itemB);
      }

      return 0
    })

    this.userSL = this.sortedList
  }

  merge(itemA: SLIngredient, itemB: SLIngredient) {
    const newAmount = +itemA.amount + +itemB.amount
    const itemC = new SLIngredient(itemA.name, newAmount, itemA.measurement, itemA.completed)
    this.sortedList.splice(this.sortedList.indexOf(itemA), 1, itemC);
    this.sortedList.splice(this.sortedList.indexOf(itemB), 1);
    // this.sortedList = Object.assign(itemA, itemB, itemC)
    // this.mergedList.push(new SLIngredient(merge.name, merge.amount, merge.measurement, merge.completed))
    // this.sortedList.push(this.mergedItem)
  }

  setUserSL() {
    this.storage.set('userSL', this.userSL)
  }

  getUserSL() {
    return this.storage.get('userSL')
      .then(
        (userSL: SLIngredient[]) => {
          this.userSL = userSL != null ? userSL : [];
          return
          this.userSL.slice();
        }
      )
  }
}

The .sort() function is where the function runs and it is the .merge() function that I find the issue with.

Here is the interface:

export class SLIngredient {

constructor(
    public name: string,
    public amount: number,
    public measurement: string,
    public completed: boolean
){}

I really appreciate any sort of help that anyone can give on this as I'm very new to it all.

Thanks again.

EDIT

When I tried calling

    sort(){
    this.userSL.reduce((reduced, element) => {
        const index = reduced.findIndex(r => r.name.toLowerCase() === element.name.toLowerCase());

        if (index === -1) return [...reduced, element];

        reduced[index].amount += element.amount;

        return reduced;
      }, []).sort((a, b) => {
        const nameA = a.name.toLowerCase()
        const nameB = b.name.toLowerCase()

        if (nameA > nameB) return 1;
        if (nameA < nameB) return -1;

        return 0
      })
}

this is the output I get in the log after adding two of the same recipe to the shopping list:

    (9) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
    0: {name: "rolled oats", amount: "1", measurement: "cup"}
    1: {name: "milled flax seed", amount: "1-2", measurement: "tbsp"}
    2: {name: "ground cinnamon", amount: "1", measurement: "tsp"}
    3: {name: "non-diary milk of your choice - Coconut is best", amount: "1.5", measurement: "cups"}
    4: {name: "desiccated coconut", amount: "1", measurement: "tbsp"}
    5: {name: "banana", amount: "1", prep: "sliced"}
    6: {name: "frozen mixed berries or fresh seasonal berries (optional)", amount: "1", measurement: "handful"}
    7: {name: "raisins (optional)", amount: "1", measurement: "handful"}
    8: {name: "cocoa powder (optional)", amount: "1", measurement: "tsp"}
    length: 9
    __proto__: Array(0)

    (18) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
    0: {name: "rolled oats", amount: "11", measurement: "cup"}
    1: {name: "milled flax seed", amount: "1-21-2", measurement: "tbsp"}
    2: {name: "ground cinnamon", amount: "11", measurement: "tsp"}
    3: {name: "non-diary milk of your choice - Coconut is best", amount: "1.51.5", measurement: "cups"}
    4: {name: "desiccated coconut", amount: "11", measurement: "tbsp"}
    5: {name: "banana", amount: "11", prep: "sliced"}
    6: {name: "frozen mixed berries or fresh seasonal berries (optional)", amount: "11", measurement: "handful"}
    7: {name: "raisins (optional)", amount: "11", measurement: "handful"}
    8: {name: "cocoa powder (optional)", amount: "11", measurement: "tsp"}
    9: {name: "rolled oats", amount: "11", measurement: "cup"}
    10: {name: "milled flax seed", amount: "1-21-2", measurement: "tbsp"}
    11: {name: "ground cinnamon", amount: "11", measurement: "tsp"}
    12: {name: "non-diary milk of your choice - Coconut is best", amount: "1.51.5", measurement: "cups"}
    13: {name: "desiccated coconut", amount: "11", measurement: "tbsp"}
    14: {name: "banana", amount: "11", prep: "sliced"}
    15: {name: "frozen mixed berries or fresh seasonal berries (optional)", amount: "11", measurement: "handful"}
    16: {name: "raisins (optional)", amount: "11", measurement: "handful"}
    17: {name: "cocoa powder (optional)", amount: "11", measurement: "tsp"}
    length: 18
    __proto__: Array(0)

SECOND EDIT

This is the part I'm still having issues with:

    addItems(items: SLIngredient[]){
    this.userSL.push(...items);
    this.sort()
    this.setUserSL();
    console.log(this.userSL)
}

sort(){
    this.mergedList = this.userSL.reduce((reduced, element) => {
        const index = reduced.findIndex(r => r.name.toLowerCase() === element.name.toLowerCase());

        if (index === -1) return [...reduced, element];

        reduced[index].amount += element.amount;

        return reduced;
      }, []).sort((a, b) => {
        const nameA = a.name.toLowerCase()
        const nameB = b.name.toLowerCase()

        if (nameA > nameB) return 1;
        if (nameA < nameB) return -1;

        return 0
      })
      this.userSL = this.mergedList
}

setUserSL(){
    this.storage.set('userSL', this.userSL)
}

So I call the addItems() function and it adds it to the array but it also merges it straight away, then for some reason it sees that ingredient as having 2 of itself and so when it's added again it doubles instead of just adding one.

The other issue is that it displays the updated value instead of just pushing that to an array that can be displayed elsewhere.

     (9) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
     0: {name: "banana/s", amount: 1, prep: "mashed"}
     1: {name: "rolled oats", amount: 1, measurement: "cup"}
     2: {name: "milled flaxseed", amount: 2, measurement: "tbsp"}
     3: {name: "ground cinnamon", amount: 1, measurement: "tsp"}
     4: {name: "non-dairy milk of your choice (I use Almond)", amount: 1.5, measurement: "cup/s"}
     5: {name: "frozen mixed berries or fresh seasonal berries (optional)", amount: 1, measurement: "handful/s"}
     6: {name: "raisins (optional)", amount: 1, measurement: "handful/s"}
     7: {name: "cocoa powder (optional)", amount: 1, measurement: "tsp"}
     8: {name: "desiccated coconut (optional)", amount: 2, measurement: "tsp"}

becomes:

(9) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
 0: {name: "banana/s", amount: 2, prep: "mashed"}
 1: {name: "cocoa powder (optional)", amount: 2, measurement: "tsp"}
 2: {name: "desiccated coconut (optional)", amount: 4, measurement: "tsp"}
 3: {name: "frozen mixed berries or fresh seasonal berries (optional)", amount: 2, measurement: "handful/s"}
 4: {name: "ground cinnamon", amount: 2, measurement: "tsp"}
 5: {name: "milled flaxseed", amount: 4, measurement: "tbsp"}
 6: {name: "non-dairy milk of your choice (I use Almond)", amount: 3, measurement: "cup/s"}
 7: {name: "raisins (optional)", amount: 2, measurement: "handful/s"}
 8: {name: "rolled oats", amount: 2, measurement: "cup"}
 length: 9
 __proto__: Array(0)

becomes

(9) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
0: {name: "banana/s", amount: 4, prep: "mashed"}
1: {name: "cocoa powder (optional)", amount: 4, measurement: "tsp"}
2: {name: "desiccated coconut (optional)", amount: 8, measurement: "tsp"}
3: {name: "frozen mixed berries or fresh seasonal berries (optional)", amount: 4, measurement: "handful/s"}
4: {name: "ground cinnamon", amount: 4, measurement: "tsp"}
5: {name: "milled flaxseed", amount: 8, measurement: "tbsp"}
6: {name: "non-dairy milk of your choice (I use Almond)", amount: 6, measurement: "cup/s"}
7: {name: "raisins (optional)", amount: 4, measurement: "handful/s"}
8: {name: "rolled oats", amount: 4, measurement: "cup"}
length: 9
__proto__: Array(0)

and it will just keep doubling every time I add something new. And the actual page with the ingredients (that should say 1 of most things) looks like this:

screenshot

Thanks again for all your input.

2 Answers 2

2

You are changing the structure of an array in sort function, and that is big no-no. Sort function returns a new array that is sorted according to sort function, it does not alter the original array. You should first use reduce to merge duplicates, and then sort an array.

userSL.reduce((reduced, element) => {
  const index = reduced.findIndex(r => r.name.toLowerCase() === element.name.toLowerCase();

  if (index === -1) return [...reduced, element];

  reduced[index].amount += element.amount;

  return reduced;
}, []).sort((a, b) => {
  const nameA = a.name.toLowerCase()
  const nameB = b.name.toLowerCase()

  if (nameA > nameB) return 1;
  if (nameA < nameB) return -1;

  return 0
})

In reduce we are adding one by one element to a resulting array (reduced), but if we find out that current element has already been added, we simply update amount of already added element. Sort is then straight forward, since array is already merged.

This is not the most efficient implementation, and it may not compile (I'm writing it directly here), but idea is simple and should work.

Sign up to request clarification or add additional context in comments.

4 Comments

Hey! Thank you very much for the help, but unfortunately that doesn't work for me for two reasons, 1) It changes the amount of the actual ingredient rather than passing a new amount to be displayed on the shopping list, 2) multiple instances of each item are still passed to the array I'm calling the function within the old .sort() function, is that correct?
This will return a new array, meaning you have to assign it to your usersSL, so you have to do this.userSL = userSL.reduce.....
Legend! Thank you very much!
Sorry to retract the correct answer status but I'm still running into trouble, I've edited the original post to reflect the new problem I'm finding. Thanks a million
0

I think you could simply filter this.sortedList

this.sortedList.filter(a => a.name !== itemA.name && a.name !== itemB.name)

And then you push your itemC in the array.

1 Comment

Sorry, I'm not sure I understood, I should call this in the .merge() function before pushing itemC to the array?

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.