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