npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

notification-renderer

v0.0.0

Published

This is a component that can dynamically instantiate Angular components inside of an given html template, and render that template. Thereby allowing you to insert Angular components into HTML that may be generated at run time, or received externally.

Readme

ng-html-renderer

This is a component that can dynamically instantiate Angular components inside of an given html template, and render that template. Thereby allowing you to insert Angular components into HTML that may be generated at run time, or received externally.


How to use

The ng-html-renderer component is given two inputs, components and html.

Components

// components

let myComponents: ComponentSelectorPair[] = [
    {
        component: MyCoolComponenent,
        selector: 'my-cool-component'
    },
    {
        component: MyOtherComponent,
        selector: 'other-thing'
    },
    // ... 
]

The components list is simply a list of ComponentSelectorPairs, which have the Angular component's class name, and a string selector by which elements will be matched in the html template. The selector does not need to match the selector used in the component's metadata.

The selector will then be expected to found on the element you wish to insert into as an attribute.

HTML

<div class="my-html-partial">
    <p>
        If you tried to just insert this template into the DOM, you wouldn't be able to do anything with these buttons, unless you tried to grab them manually and add your own event handlers, styles, manual bindings, etc...
    </p>
    
    <!-- You can't point this onclick handler at your component class code at 'compile' time -->
    <button onclick="myFunc($evevnt)"></button>

    <div my-cool-component></div>

    <span other-thing data-myCaption="custom value"></span>
</div>

Your html is an html partial, that is written as standard html. It is not necessary to write the entire html document (<html> tag, <head> tag, <body> tag...). Attributes are added to elements corresponding to the given selectors for components you want to render in your html. You can add data-* attributes as well for data you want to pass to the component, as though it were through an @Input(). However it is not truly data-bound, and will only write once. Additionally, all values will be passed as strings. If you need a value as a type other than a string, you will need to convert it yourself. Although at runtime the attributes are in lowercase, the renderer will try to match them to fields in your component by a case-insensitive test.

ng-html-renderer

// You have your template/components variables

let myTemplate = `<div class="my-html-partial">
    <p>I'm an HTML template that's really just a string. Observe that I'm just a partial, not a full document</p>

    <p>Technically any string is valid html not matter how badly formatted

    <span></p> like this incorrect closure here.</span>

    <p> It doesn't matter what kind of element you put your component selector on.</p>
    <span my-cool-component></span>

    <p>But some cases may be incredibly impractical</p>
    <ul my-component-that-doesn't-resolve-to-lis></ul>

    <p class="fancy-paragraph">You can still put classes on your html elements, and those classes should resolve in your global style scope.</p>

    <p class="my-component-scoped-css-class">Angular adds attribute selectors to scope component css, so they won't go beyond the component, up or down, without ::ng-deep</p>

    <p>In an ideal scenario, the consumer should be aware of classes the template may have, and provide style rules for those classes.</p>

</div>`;

let myComponents: ComponentSelectorPair[] = [
    {
        component: MyCoolComponenent,
        selector: 'my-cool-component'
    },
    {
        component: MyOtherComponent,
        selector: 'other-thing'
    },
    // ... 
]
<div class="your-consumer-component-context">

    <!-- provide your values through typical data binding -->
    <ng-html-renderer [components]="myComponents" [template]="myTemplate"></ng-html-renderer>
</div>

After you pass at least a template html value, the ng-html-renderer will then attempt to render your html. Providing components is not necessary, and is advised if it is known ahead of time you won't want to render any new Angular components inside the template (Though that would defeat the purpose of using this component). The renderer will then after appending to the DOM, search the tree for any elements that contain attributes matching the selectors provided, and instantiate the associated component. The component will go through the regular Angular lifecycle and dependency injections as though it was rendered through normal means.

The renderer has one event, renderFinished which will emit when the renderer has fully completed the process of html parsing, appending to the DOM, and inserting dynamically created Angular components.

<div class="your-consumer-component-context">

    <!-- provide your values through typical data binding -->
    <ng-html-renderer [components]="myComponents" [template]="myTemplate" (renderFinished)="onRenderFinish($event)"></ng-html-renderer>
</div>
onRenderFinish(components: ComponentRef<any>[]) {
    // components constitutes a list of ComponentRefs corresponding to the components create dynamically. From here you have references to these components by which you can manually interact with them.
    (components[0].componentInstance as MyCoolComponent).someEvent.subscribe(() => {});
    // ComponentRefs have a field denoting their component type, so you shouldn't need to blindly cast things
}

The renderer will destroy any dynamically instantiated components when either itself is destroyed or if it renders new html. There should be no need for the consumer to need manage the lifecycle or lifetime of dynamically created components.

Styling

Since component styles cannot normally apply beyond the scope of the component in which they're defined, in order to correctly style elements in the rendered template, styles need to be available in a global way.

One way to do this would be to define styles in a global stylesheet, or define them in the ng-html-renderer consumer as use the ::ng-deep selector to make shadow piercing styles.


How it works

When html is set, the renderer begins the process of working through the template.

ngOnDestroy() {
    this.destoryAllRenderedComponents();
    this.destroyComponentFactories();
}

/**
 * Attempt to parse the given HTML and add it to the DOM, and render the angular components
 * @param html string representing HTML to attempt to render.
 */
private parseToDomElements(html: string): void {
    // destroy what we've made so far.
    this.ngOnDestroy();

    //...
}

The first step of the render process is to attempt to try and clean up after our last render attempt by destroying what has been rendered so far and cleaning up potentially stale component factories.

// add the html to our component
this.notifWrapper.nativeElement.innerHTML = html;

// do not attempt further rendering if there are no components to render
if (this.components) {
    this.renderAngularComponents(content);
}

We then append the created DOM tree to our wrapper element. If we were provided components, we proceed to render them.

It is important to note that the rendered will not make any attempt to sanitize your HTML for malicious javascript or links, it is your responsibility to only use HTML that you trust.

Why not? Angular's built in DOM sanitizer implementation also strips attributes and CSS clases which are fundamental to how the renderer works.

/**
 * Try and render the angular components in given element
 * @param root element to check for and render angular components under
 */
private renderAngularComponents(root: Element) {
    // for each element we should be able to render
    for (let component of this.components) {
        // find each of those elements in the template, and for each of them...
        root.querySelectorAll(`*[${component.selector}]`).forEach(el => {

        // We can add the component onto an arbitrary DOM node, this case being the element from the query selector
        let cmpRef = this.createComponent(component.selector, el);

        // ...

We iterate through the components, on each iteration we search the root of our small tree and down for elements with the component selector as an attribute. For each found instance we create the associated component.

/**
 * Create a component, generates the component factory if necessary
 * @param selector The selector of the component we want to make
 * @param element the element to make the component on
 */
private createComponent(selector: string, element: Element): ComponentRef<Type<any>> {
    if (!(selector && selector.length && element)) {
      let haveSelector = selector && selector.length;
      let bothMissing = !(haveSelector || element);

      let err = new Error(`${haveSelector ? '' : 'selector'}${bothMissing ? ' and ' : ''}${element ? '' : 'element'} ${bothMissing ? 'were' : 'was'} expected to be a non null value`)
      throw err;
    }

    let componentType = this.componentSelectorMap[selector];
    let componentFactory: ComponentFactory<Type<any>>;

    let makeComponentFactory = () => {
      componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);
      this.componentFactories[selector] = componentFactory;
    }

    // if the componentFactories object exists
    if (!(this.componentFactories == null && this.componentFactories == undefined)) {
      // try to get the factory we want
      componentFactory = this.componentFactories[selector];
      if (componentFactory == null || componentFactory == undefined) {
        // if we didn't get it, make the factory
        makeComponentFactory();
      }
    }
    else {
      // the componentFactories object doesn't exist at all, must make the factory keeper
      this.componentFactories = {};
      makeComponentFactory();
    }

    let component = componentFactory.create(this.injector, [], element);

    return component;
}

We check to see if we have already made this component factory. If we have made it already, retrieve the reference, otherwise create the factory, if the bookkeeping doesn't exist, create that too. Then create the component and return it. The created component will be put inside the given element, and passed this component's injector reference.

// We can add the component onto an arbitrary DOM node, this case being the element from the query selector
let cmpRef = this.createComponent(component.selector, el);

// attempt to parse out extra data items and give them to the dynamic component. 
let attrList = Array.from(el.attributes);

this.attemptToAssignComponentInputs(attrList, cmpRef);

We check this element for data attributes intended to pass along to the component. This checks for attributes that match the pattern data-*. For those found attributes, the remainder of the key (suppose data-myField, myField is used) is used to add the attribute value to the component. For these purposes, the component instance is treated as a simple javascript object. The value will be added irrespective of if the component's class. Therefore, fields that are included as data attributes will be added to the component, regardless of if the component's class defines them. However, these values will not be available for use at compile time without skipping type checks.

if (this.componentReferences == null || this.componentReferences == undefined) {
    this.componentReferences = [];
}
// add this guy to our reference list
this.componentReferences.push(cmpRef);

The last part of creating the Angular component is to push the created component reference on to our list of created components, so we can later destory it.

// emit the component references 
let emission = this.componentReferences ? this.componentReferences.slice() : null;
this.renderFinished.emit(emission);

After the component render process is down, we emit the renderFinished event with the component references.


Packaged Types

ComponentSelectorPair

interface ComponentSelectorPair {
    componentType: Type<any>;
    selector: string;
}

The ComponentSelectorPair interface represents a component to render and the attribute selector by which an intended creation will be made. componentType should be the class of the Angular component to render. selector is the attribute that will be selected by.

Angular 16+ Migration

Angular 16 is required to utilize this package version. Please use node 18.19.1 and npm 6.14.4.

The NgHtmlRendererModule is no longer a required import, as the HTMLComponentRendererComponent this library exports has been converted to standalone. Consumers that wish to use the renderer must include the import inside of the @Component declaration:

@Component({
    selector: 'your-component',
    templateUrl: './your-component.component.html',
    styleUrls: ['./your-component.component.scss'],
    standalone: true,
    imports: [HTMLComponentRendererComponent]
})