ngx-dynamic-hooks
Version:
Automatically insert live Angular components into a dynamic string of content (based on their selector or any pattern of your choice) and render the result in the DOM.
53 lines • 8.4 kB
JavaScript
import { isAngularManagedElement } from './services/utils/utils';
import { contentElementAttr } from './constants/core';
/**
* A function that observes an HTMLElement and triggers a callback when new elements are added to it.
* Does NOT trigger for Angular components or logic, only for neutral HTML elements.
*
* @param content - The HTMLElement to watch for element additions
* @param callbackFn - The callback function to call when a change occurs. Will be called with the closest parent element of all added elements.
*/
export const observeElement = (content, callbackFn) => {
const observer = new MutationObserver((mutationsList, observer) => {
// Collect only addded nodes
let newNodes = [];
for (const mutation of mutationsList) {
mutation.addedNodes.forEach(addedNode => newNodes.push(addedNode));
mutation.removedNodes.forEach(removedNode => newNodes = newNodes.filter(newNode => newNode !== removedNode));
}
// Ignore new nodes created as part of Angular component views
newNodes = newNodes.filter(newNode => newNode.nodeType === 1 && !isAngularManagedElement(newNode) || // Check HTMLElements
newNode.nodeType === 3 && !isAngularManagedElement(newNode.parentNode) // Check text node parents
);
// Ignore new nodes that are children of a content element that is currently being parsed (lots of elements get created/removed during that time)
newNodes = newNodes.filter(newNode => {
const element = newNode.nodeType === 1 ? newNode : newNode.parentElement;
return element.closest(`[${contentElementAttr}]`) === null;
});
if (newNodes.length) {
// Find closest common parent
const commonParent = findClosestCommonParent(newNodes);
// Run callback
callbackFn(commonParent);
}
});
observer.observe(content, { childList: true, subtree: true });
return observer;
};
/**
* Finds the closest common parent element for multiple elements
*
* @param elements - The elements in question
*/
const findClosestCommonParent = (elements) => {
if (elements.length === 0)
return null;
let parent = elements[0];
for (const element of elements) {
while (parent === element || !parent.contains(element)) {
parent = parent.parentElement;
}
}
return parent;
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhbmRhbG9uZUhlbHBlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL25neC1keW5hbWljLWhvb2tzL3NyYy9saWIvc3RhbmRhbG9uZUhlbHBlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNqRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUV0RDs7Ozs7O0dBTUc7QUFDSCxNQUFNLENBQUMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxPQUFvQixFQUFFLFVBQWdELEVBQW9CLEVBQUU7SUFDekgsTUFBTSxRQUFRLEdBQUcsSUFBSSxnQkFBZ0IsQ0FBQyxDQUFDLGFBQWEsRUFBRSxRQUFRLEVBQUUsRUFBRTtRQUVoRSw0QkFBNEI7UUFDNUIsSUFBSSxRQUFRLEdBQVcsRUFBRSxDQUFDO1FBQzFCLEtBQUssTUFBTSxRQUFRLElBQUksYUFBYSxFQUFFLENBQUM7WUFDckMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7WUFDbkUsUUFBUSxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sS0FBSyxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBQy9HLENBQUM7UUFFRCw4REFBOEQ7UUFDOUQsUUFBUSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FDbkMsT0FBTyxDQUFDLFFBQVEsS0FBSyxDQUFDLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxPQUFPLENBQUMsSUFBZSxxQkFBcUI7WUFDL0YsT0FBTyxDQUFDLFFBQVEsS0FBSyxDQUFDLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxPQUFPLENBQUMsVUFBVyxDQUFDLENBQUcsMEJBQTBCO1NBQ3JHLENBQUM7UUFFRixpSkFBaUo7UUFDakosUUFBUSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEVBQUU7WUFDbkMsTUFBTSxPQUFPLEdBQWdCLE9BQU8sQ0FBQyxRQUFRLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFzQixDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsYUFBYyxDQUFDO1lBQ3RHLE9BQU8sT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLGtCQUFrQixHQUFHLENBQUMsS0FBSyxJQUFJLENBQUM7UUFDN0QsQ0FBQyxDQUFDLENBQUM7UUFFSCxJQUFJLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNwQiw2QkFBNkI7WUFDN0IsTUFBTSxZQUFZLEdBQUcsdUJBQXVCLENBQUMsUUFBUSxDQUFFLENBQUM7WUFFeEQsZUFBZTtZQUNmLFVBQVUsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUMzQixDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7SUFFSCxRQUFRLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFFOUQsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQyxDQUFBO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sdUJBQXVCLEdBQUcsQ0FBQyxRQUFnQixFQUFvQixFQUFFO0lBQ3JFLElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDdkMsSUFBSSxNQUFNLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRXpCLEtBQUssTUFBTSxPQUFPLElBQUksUUFBUSxFQUFFLENBQUM7UUFDL0IsT0FBTyxNQUFNLEtBQUssT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1lBQ3ZELE1BQU0sR0FBRyxNQUFNLENBQUMsYUFBYyxDQUFDO1FBQ2pDLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxNQUFxQixDQUFDO0FBQy9CLENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGlzQW5ndWxhck1hbmFnZWRFbGVtZW50IH0gZnJvbSAnLi9zZXJ2aWNlcy91dGlscy91dGlscyc7XG5pbXBvcnQgeyBjb250ZW50RWxlbWVudEF0dHIgfSBmcm9tICcuL2NvbnN0YW50cy9jb3JlJztcblxuLyoqXG4gKiBBIGZ1bmN0aW9uIHRoYXQgb2JzZXJ2ZXMgYW4gSFRNTEVsZW1lbnQgYW5kIHRyaWdnZXJzIGEgY2FsbGJhY2sgd2hlbiBuZXcgZWxlbWVudHMgYXJlIGFkZGVkIHRvIGl0LlxuICogRG9lcyBOT1QgdHJpZ2dlciBmb3IgQW5ndWxhciBjb21wb25lbnRzIG9yIGxvZ2ljLCBvbmx5IGZvciBuZXV0cmFsIEhUTUwgZWxlbWVudHMuXG4gKiBcbiAqIEBwYXJhbSBjb250ZW50IC0gVGhlIEhUTUxFbGVtZW50IHRvIHdhdGNoIGZvciBlbGVtZW50IGFkZGl0aW9uc1xuICogQHBhcmFtIGNhbGxiYWNrRm4gLSBUaGUgY2FsbGJhY2sgZnVuY3Rpb24gdG8gY2FsbCB3aGVuIGEgY2hhbmdlIG9jY3Vycy4gV2lsbCBiZSBjYWxsZWQgd2l0aCB0aGUgY2xvc2VzdCBwYXJlbnQgZWxlbWVudCBvZiBhbGwgYWRkZWQgZWxlbWVudHMuXG4gKi9cbmV4cG9ydCBjb25zdCBvYnNlcnZlRWxlbWVudCA9IChjb250ZW50OiBIVE1MRWxlbWVudCwgY2FsbGJhY2tGbjogKHBhcmVudEVsZW1lbnQ6IEhUTUxFbGVtZW50KSA9PiB2b2lkKTogTXV0YXRpb25PYnNlcnZlciA9PiB7XG4gIGNvbnN0IG9ic2VydmVyID0gbmV3IE11dGF0aW9uT2JzZXJ2ZXIoKG11dGF0aW9uc0xpc3QsIG9ic2VydmVyKSA9PiB7XG5cbiAgICAvLyBDb2xsZWN0IG9ubHkgYWRkZGVkIG5vZGVzXG4gICAgbGV0IG5ld05vZGVzOiBOb2RlW10gPSBbXTtcbiAgICBmb3IgKGNvbnN0IG11dGF0aW9uIG9mIG11dGF0aW9uc0xpc3QpIHtcbiAgICAgIG11dGF0aW9uLmFkZGVkTm9kZXMuZm9yRWFjaChhZGRlZE5vZGUgPT4gbmV3Tm9kZXMucHVzaChhZGRlZE5vZGUpKTtcbiAgICAgIG11dGF0aW9uLnJlbW92ZWROb2Rlcy5mb3JFYWNoKHJlbW92ZWROb2RlID0+IG5ld05vZGVzID0gbmV3Tm9kZXMuZmlsdGVyKG5ld05vZGUgPT4gbmV3Tm9kZSAhPT0gcmVtb3ZlZE5vZGUpKTtcbiAgICB9XG5cbiAgICAvLyBJZ25vcmUgbmV3IG5vZGVzIGNyZWF0ZWQgYXMgcGFydCBvZiBBbmd1bGFyIGNvbXBvbmVudCB2aWV3c1xuICAgIG5ld05vZGVzID0gbmV3Tm9kZXMuZmlsdGVyKG5ld05vZGUgPT4gXG4gICAgICBuZXdOb2RlLm5vZGVUeXBlID09PSAxICYmICFpc0FuZ3VsYXJNYW5hZ2VkRWxlbWVudChuZXdOb2RlKSB8fCAgICAgICAgICAgIC8vIENoZWNrIEhUTUxFbGVtZW50c1xuICAgICAgbmV3Tm9kZS5ub2RlVHlwZSA9PT0gMyAmJiAhaXNBbmd1bGFyTWFuYWdlZEVsZW1lbnQobmV3Tm9kZS5wYXJlbnROb2RlISkgICAvLyBDaGVjayB0ZXh0IG5vZGUgcGFyZW50c1xuICAgICk7XG5cbiAgICAvLyBJZ25vcmUgbmV3IG5vZGVzIHRoYXQgYXJlIGNoaWxkcmVuIG9mIGEgY29udGVudCBlbGVtZW50IHRoYXQgaXMgY3VycmVudGx5IGJlaW5nIHBhcnNlZCAobG90cyBvZiBlbGVtZW50cyBnZXQgY3JlYXRlZC9yZW1vdmVkIGR1cmluZyB0aGF0IHRpbWUpXG4gICAgbmV3Tm9kZXMgPSBuZXdOb2Rlcy5maWx0ZXIobmV3Tm9kZSA9PiB7XG4gICAgICBjb25zdCBlbGVtZW50OiBIVE1MRWxlbWVudCA9IG5ld05vZGUubm9kZVR5cGUgPT09IDEgPyBuZXdOb2RlIGFzIEhUTUxFbGVtZW50IDogbmV3Tm9kZS5wYXJlbnRFbGVtZW50ITtcbiAgICAgIHJldHVybiBlbGVtZW50LmNsb3Nlc3QoYFske2NvbnRlbnRFbGVtZW50QXR0cn1dYCkgPT09IG51bGw7XG4gICAgfSk7XG5cbiAgICBpZiAobmV3Tm9kZXMubGVuZ3RoKSB7XG4gICAgICAvLyBGaW5kIGNsb3Nlc3QgY29tbW9uIHBhcmVudFxuICAgICAgY29uc3QgY29tbW9uUGFyZW50ID0gZmluZENsb3Nlc3RDb21tb25QYXJlbnQobmV3Tm9kZXMpITtcblxuICAgICAgLy8gUnVuIGNhbGxiYWNrXG4gICAgICBjYWxsYmFja0ZuKGNvbW1vblBhcmVudCk7XG4gICAgfVxuICB9KTtcblxuICBvYnNlcnZlci5vYnNlcnZlKGNvbnRlbnQsIHsgY2hpbGRMaXN0OiB0cnVlLCBzdWJ0cmVlOiB0cnVlIH0pO1xuXG4gIHJldHVybiBvYnNlcnZlcjtcbn1cblxuLyoqXG4gKiBGaW5kcyB0aGUgY2xvc2VzdCBjb21tb24gcGFyZW50IGVsZW1lbnQgZm9yIG11bHRpcGxlIGVsZW1lbnRzXG4gKiBcbiAqIEBwYXJhbSBlbGVtZW50cyAtIFRoZSBlbGVtZW50cyBpbiBxdWVzdGlvblxuICovXG5jb25zdCBmaW5kQ2xvc2VzdENvbW1vblBhcmVudCA9IChlbGVtZW50czogTm9kZVtdKTogSFRNTEVsZW1lbnR8bnVsbCA9PiB7XG4gIGlmIChlbGVtZW50cy5sZW5ndGggPT09IDApIHJldHVybiBudWxsO1xuICBsZXQgcGFyZW50ID0gZWxlbWVudHNbMF07XG5cbiAgZm9yIChjb25zdCBlbGVtZW50IG9mIGVsZW1lbnRzKSB7XG4gICAgd2hpbGUgKHBhcmVudCA9PT0gZWxlbWVudCB8fCAhcGFyZW50LmNvbnRhaW5zKGVsZW1lbnQpKSB7XG4gICAgICBwYXJlbnQgPSBwYXJlbnQucGFyZW50RWxlbWVudCE7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHBhcmVudCBhcyBIVE1MRWxlbWVudDtcbn0iXX0=