1

I have this array:

data: [
  ‘big text 1 here.’,
  ‘big text 2 here followed by list:’,
  '-this is list item;',
  '-this is another list item;',
  ‘big text 3 here followed by list:’,
  '-this is list item;',
  '-this is another list item;',
  '-this is another list item;',
  ‘big text 4 here.’
],

I want to turn it to something like:

data: [
  { 
   text: ‘big text 1 here.’
   list: []
  },
  { 
   text: ‘big text 2 here followed by list:’
   list: [
     '-this is list item;',
     '-this is another list item;'
   ]
  },
  { 
   text: ‘big text 3 here followed by list:’
   list: [
     '-this is list item;',
     '-this is another list item;',
     '-this is another list item;',
   ]
  },
  { 
   text: ‘big text 4 here.’
   list: []
  },
],

I am trying to achieve this this way

interface item {
  text: string;
  list: string[];
};

const newArr: item[] = [];

this.item.content.forEach((x, i) => {
  if(x.startsWith('-') && x.endsWith(';')) {
    //this is bullet list, need to add that to the previous item...
  } else {
    newArr.push({ text: x, list: []});
  }
});

I am not sure if I am doing it in a right way, because there is no way I can think of how to access previous element in newArr; Any ideas how to do it proper way?

That is the output that I want to have based on that data:

<p>big text 1 here</p>
<p>big text 2 here followed by list:</p>
<ul>
  <li>this is list item</li>
  <li>this is another list item</li>
</ul>
<p>big text 3 here followed by list</p>
<ul>
  <li>this is list item</li>
  <li>this is another list item</li>
  <li>this is another list item</li>
<p>big text 4 here</p>
Sergino
  • 10,128
  • 30
  • 98
  • 159

4 Answers4

1

I'd use Array.reduce for this

interface DataItem {
    text: string
    list: string[]
}

function isListItem(item: string): boolean {
    return item.startsWith('-') && item.endsWith(';')
}

const transformedData = data.reduce<DataItem[]>((acc, item) => {
    // if item is list item
    if (isListItem(item)) {
        // get last DataItem from acc
        const lastDataItem = acc[acc.length - 1]

        // and use it as list item's parent
        if (lastDataItem) {
            lastDataItem.list.push(item)
        } else {
            console.error(`Parent text not found for list item ${item}`)
        }

        return acc
    }

    // if item is not list item, use it as new parent/DataItem
    const dataItem: DataItem = {
        text: item,
        list: []
    }

    return [...acc, dataItem]
}, [])

Playground link

BorisTB
  • 1,686
  • 1
  • 17
  • 26
  • any ideas on this one https://stackoverflow.com/questions/72013461/how-to-alter-reduce-function-so-it-produce-different-result ? – Sergino Apr 26 '22 at 11:52
1

I would go for ngTemplateOutlet option for generating li based on your list array.

<ul *ngFor="let item of data">
   <ng-container
       *ngTemplateOutlet="
          !(item.startsWith('-') && item.endsWith(';')) ? ulTemplate : bulletItem;
         context: { $implicit: item }
     "
     >
   </ng-container>
</ul>

<ng-template #ulTemplate let-item>
   <li>{{ item }}</li>
</ng-template>

<ng-template #bulletItem let-item>
  <ul>
    <li>{{ item }}</li>
  </ul >
</ng-template>

Now, this way you have redere the right ng-template based on your start character. Complete demo in this link Stackblitz Link

GRD
  • 208
  • 2
  • 6
  • It is interesting idea but this method creates unwanted html structure. And secondly only bulletItems should be wrapped into
      as
    • items. To solve the second issue the `"ul *ngFor="let item of data"` can be changed to div `*ngFor="let item of data"`, and `#ulTemplate
    • ` can be changed to `

      ` tag. But then each `

    • ` element will be wrapped in to `
        ` if you see what I mean https://stackblitz.com/edit/angular-ivy-221qf4?file=src%2Fapp%2Fapp.component.html
    – Sergino Mar 30 '22 at 00:51
  • @sreginogemoh then what type of structure did you wanted ? Can you show any image for reference ? – GRD Mar 30 '22 at 18:59
  • just updated the questions with such details – Sergino Mar 31 '22 at 03:34
  • @sreginogemoh then I slightly updated code here https://stackblitz.com/edit/angular-ivy-ztxgjc?file=src%2Fapp%2Fapp.component.html Please check this out and let me know further. Thanks! – GRD Apr 01 '22 at 09:04
0

You could try the following below snippet

let data = [
  'big text 1 here.',
  'big text 2 here followed by list:',
  '-this is list item;',
  '-this is another list item;',
  'big text 3 here followed by list:',
  '-this is list item;',
  '-this is another list item;',
  '-this is another list item;',
  'big text 4 here.'
];

function format_array_to_object(data) {
    let new_data = [];
  let previous_data = {};
  data.forEach( function( item, key ){
    if ( item.trim().charAt(0) === '-' ) {
       previous_data.list.push( item );
    } else {
      if ( Object.keys(previous_data).length !== 0 ) {
        new_data.push(previous_data);
        previous_data = {};
        let current_elem = { text: item, list:[] };
        previous_data = current_elem;
      } else {
        let current_elem = { text: item, list:[] };
        previous_data = current_elem;
      }

      if ( ( key + 1 ) === data.length ) {
        new_data.push(previous_data);
      }

    }
  } );
  return new_data;
}

console.log(format_array_to_object(data));
Mahedi Hasan
  • 178
  • 1
  • 11
-1

Just saying your strings don't have the correct quotations marks, and ' are not the same and won't work for a string.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Silkyway
  • 64
  • 7