1

I have a product form which I would like users to add variants of this product to. My object for the main settings

{
  title: 'Bike',
  settings: {
    options: [
      {
        title: 'Size',
        values: ['60cm', '80cm', '120cm']
      },
      {
        title: 'Color',
        values: ['White', 'Black', 'Red']
      }
    ],
    variants: []
  }
};

So for each option, I need a variant with each title and it's value - which will be regenerated and passed back to the .settings.variants above.

I have created a Stackblitz https://stackblitz.com/edit/angular-pi27mf

In this method I need to generate a brand new variants[], right now I only understand how to generate something like

{Size: "60cm", Color: "White", Image: "img.png"},
{Size: "60cm", Color: "Black", Image: "img.png"},
{Size: "60cm", Color: "Red", Image: "img.png"},

By just looping through one of the options.. but how can I loop though all options and create a new variant for each possible value?

The model I would need to achieve would be this

{Size: "60cm", Color: "White", Image: "img.png"},
{Size: "60cm", Color: "Black", Image: "img.png"},
{Size: "60cm", Color: "Red", Image: "img.png"},
{Size: "80cm", Color: "White", Image: "img.png"},
{Size: "80cm", Color: "Black", Image: "img.png"},
{Size: "80cm", Color: "Red", Image: "img.png"},
{Size: "120cm", Color: "White", Image: "img.png"},
{Size: "120cm", Color: "Black", Image: "img.png"},
{Size: "120cm", Color: "Red", Image: "img.png"},

Keeping in mind that Size and Color are dynamic and could be changed by the user, so the function can't just depend on this amount/type of options.

I am sure there is some array math that needs to be going on but if someone could point me in the right direction or re-create a stackblitz for me it would be a massive help.

Jayden
  • 1,029
  • 3
  • 9
  • 15

3 Answers3

1

You can create an array of title and a 2D array of values array using map. Then create a Cartesian product of all the values. Then loop through the all the combinations and create objects using reduce. Use { Image: "img.png" } as the initialValue parameter. This will work for any number of objects inside options array

const input = { title: 'Bike', settings: { options: [{ title: 'Size', values: ['60cm', '80cm', '120cm'] }, { title: 'Color', values: ['White', 'Black', 'Red'] }], variants: [] } }

const { options } = input.settings,
      titles = options.map(o => o.title),
      values = options.map(o => o.values),
      defaultObject = { Image: "img.png" }

// https://stackoverflow.com/a/57597533/3082296
const combos = values.reduce((acc, curr, i) =>
  acc.flatMap(c => curr.map(n => [].concat(c, n)))
)

const createObject = values =>
  titles.reduce((acc, k, i) => {
    acc[k] = values[i];
    return acc
  }, defaultObject)

const output = combos.map(createObject)

console.log(...output)
adiga
  • 34,372
  • 9
  • 61
  • 83
  • What do I need to import for flatMap? `Property 'flatMap' does not exist on type 'string[]'.` – Jayden Jan 02 '20 at 10:00
  • Typescript declaration file (`.d.ts`) doesn't have `Array.prototype.flatMap`. So, the compiler is complaining that `flatMap` is not an array extension [Typescript flatMap, flat, flatten doesn't exist on type any\[\]](https://stackoverflow.com/questions/53556409) – adiga Jan 02 '20 at 10:03
1

You could take the keys and values from data, generate the cartesian product from values and create key/value pairs as result.

var data = { title: 'Bike', settings: { options: [{ title: 'Size', values: ['60cm', '80cm', '120cm'] }, { title: 'Color', values: ['White', 'Black', 'Red'] }], variants: [] } },
    keys = data.settings.options.map(({ title }) => title).concat('Image'),
    values = data.settings.options.map(({ values }) => values).concat([['img.png']]),
    cartesian = values
        .reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []))
        .map(a => Object.assign({ title: a.slice(0, -1).join('/') }, ...keys.map((k, i) => ({ [k]: a[i] }))));

console.log(cartesian);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 3
    @Jayden change `Object.assign(...keys.map((k, i)` to `Object.assign({},...keys.map((k, i)` – Ramesh Reddy Jan 02 '20 at 10:03
  • Would you know to concat a title variable which in the value contains all variant, producing `{Size: '60cm', Color: 'White', Title: '60cm / White', Img: ''}` ? – Jayden Jan 07 '20 at 07:36
0

Try this:

  generateVariants() {
    const options = [...this._form.settings.options];
    const sizeOptions = options[0].values;
    const colorOptions = options[1].values;
    const regeneratedVariants = [];
    for(let i=0;i< sizeOptions.length;i++) {
      for(let j=0;j< colorOptions.length;j++) {
        regeneratedVariants.push({
          Size: sizeOptions[i],
          Color: colorOptions[i],
          Image: "img.png"
        })
      }
    }
    console.log(regeneratedVariants);
  }

this will produce an array with all the variants possible with sizes and colors.

Ramesh Reddy
  • 10,159
  • 3
  • 17
  • 32
  • Sorry I should have explained better, but `size` and `color` would be dynamic, so the model posted may differ to what the user is actually inputting. – Jayden Jan 02 '20 at 09:38
  • You can still use this logic to generate variants, you just have to figure out a way to get the size and color or any other options. – Ramesh Reddy Jan 02 '20 at 09:40