'Wondering how to achieve something similar to *ngFor/ng-repeat in jquery or JS

Is it possible to have a similar concept of ngFor or ng-repeat in jQuery or vanilla JS?

Want to do something similar but not in angular way.

<div *ngFor="let inputSearch of searchBoxCount" class="col-sm-12">
    <textarea name="{{inputSearch.name}}" id="{{inputSearch.name}}" rows="2" class="search-area-txt" attr.placeholder="{{placeholder}} {{inputSearch.name}}">
    </textarea>
</div>

maybe use with the data="" attribute, whichever make sense.



Solution 1:[1]

If you want it in javascript you have to create elements dynamically in a loop. *ngFor and ngRepeat in angular are directives that contains template bindings that we can't have either in javascript or jquery. Once the directives encounters angular try to render the respective templates till the loop ends. Anyway, by javascript we can do like this. If you want to append 6 divs to the body element with respective id, You have to do like below.

var array = ['first', 'second', 'third', .......so on];
for(var i=0; i<array.length; i++){
   var elem = document.createElement("div");
   elem.setAttribute('id', array[i]);
   document.body.appendchild(elem);
}

We can't do it as we did in angular.

Solution 2:[2]

    const anArray = [
        { tittle: "One", body: "Example 1" },
        { tittle: "Two", body: "Example 2" }
    ]
    
    function ngForFunctionality() {
        let value = '';
        anArray.forEach((post) => {
            value += `<li>${post.tittle} - ${post.body}</li>`;
        });
        document.body.innerHTML = value;
    };
    
    ngForFunctionality();
<body></body>

Solution 3:[3]

In JQuery, I'd do something like this:

HTML:

<div class="searchbox-container col-sm-12">        
</div>

JavaScript:

var textAreas = $.map(searchBoxCount, function(inputSearch) {
    return $("<textarea></textarea")
      .attr({
        name: inputSearch.name,
        id: inputSearch.name
        rows: "2",
        placeholder: placeholder + " " + inputSearch.name
      })
      .addClass("search-area-txt");
});

$('.searchbox-container').append(textAreas);

Solution 4:[4]

There is no easy way to get a similar ngFor by vanilla. But it's possible!

My implementation (You can make it better using more regex):

HTML code:

   <ul id="my-list">
      <li *for="let contact of contactsList">
        <span class="material-icons">{{ contact.icon }}</span>
        <span>{{ contact.value }}</span>
      </li>
    </ul>

JS code for implement *for like Angular's *ngFor:

/**
 * (*for) cicle implementation
 * @param element the root element from HTML part where you want to apply (*for) cicle. This root element cannot to use (*for). Just children are allowed to use it.
 * @returns void
 */
function htmlFor(element) {
  return replace(element, element);
}

/**
 * Recursive function for map all descendants and replace (*for) cicles with repetitions where items from array will be applied on HTML.
 * @param rootElement The root element
 * @param el The mapped root and its children
 */
function replace(rootElement, el) {
  el.childNodes.forEach((childNode) => {
    if (childNode instanceof HTMLElement) {
      const child = childNode;
      if (child.hasAttribute('*for')) {
        const operation = child.getAttribute('*for');
        const itemsCommand = /let (.*) of (.*)/.exec(operation);
        if (itemsCommand?.length === 3) {
          const listName = itemsCommand[2];
          const itemName = itemsCommand[1];

          if (rootElement[listName] && Array.isArray(rootElement[listName])) {
            for (let item of rootElement[listName]) {
              const clone = child.cloneNode(true);
              clone.removeAttribute('*for');
              const htmlParts = clone.innerHTML.split('}}');
              htmlParts.forEach((part, i, parts) => {
                const position = part.indexOf('{{');

                if (position >= 0) {
                  const pathTovalue = part
                    .substring(position + 2)
                    .replace(/ /g, '');
                  const prefix = part.substring(0, position);

                  let finalValue = '';
                  let replaced = false;

                  if (pathTovalue.indexOf('.') >= 0) {
                    const byPatternSplitted = pathTovalue.split('.');
                    if (byPatternSplitted[0] === itemName) {
                      replaced = true;
                      for (const subpath of byPatternSplitted) {
                        finalValue = item[subpath];
                      }
                    }
                  } else {
                    if (pathTovalue === itemName) {
                      replaced = true;
                      finalValue = item;
                    }
                  }
                  parts[i] = prefix + finalValue;
                }

                return part;
              });

              clone.innerHTML = htmlParts.join('');

              el.append(clone);
            }
          }
        }
        el.removeChild(child);
      }
      replace(rootElement, child);
    }
  });
}

Finally, in your component code:

document.addEventListener('DOMContentLoaded', () => {
  const rootElement = document.getElementById('my-list');

  rootElement.contactsList = [
    {
      icon: 'icon-name',
      value: 'item value here',
    },
    ...
  ];

  htmlFor(rootElement);
});

Finished. You have a *for on your vanilla code.

If anyone wants to experiment a performance comparison of this *for with the Angular's *ngFor, please share it with me, as I'm curious.

Code on stackblitz

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4