Search code examples
angularangular-materialangular2-templateangular-material-table

Angular Template - Dynamically generated HTML is output as a string and not HTML


I am modifying an Angular template that is currently using innerHTML to dynamically build HTML fragments into the DOM. In this case, I am creating a mat-table and dynamically building the header.

Here is the existing template code before my changes. This works as expected calling the buildLabelFromValues() through the [innerHtml] directive:

<div class="mat-table">
    <!--HEADER-->
    <div class="mat-header-row mat-option no-pointer optgroup-header table mat-primary"
      [innerHtml]="'localStr.' + buildLabelFromValues(autoCompleteList[0], true) | stringLocalize | async">
    </div>
    ...
</div>

However, I need to explicitly build the table header in a more "Angular" way (in order to prepare for setting click/change listeners on the header cells). I can't do this through elements created from the innerHtml directive. So, my modified template code is below:

<div class="mat-header-row mat-option no-pointer optgroup-header table mat-primary">
 {{ this.buildHeaderLabelFromValues(autoCompleteList[0]) }}
</div>

The issue I have currently, is that with the modified template code above, the response from buildHeaderLabelFromValues() is an htmlEncoded string of characters. I need the output to be in actual HTML.

enter image description here

Finally, here is the method that builds the header:

buildHeaderLabelFromValues(value: any): string {
    console.log('value:', value);
    let label = '';
    forEach(value, (_value: any, _key: string) => {
      if (!this.hiddenColumns.includes(_key)) {
        if (_value === null) { _value = undefined; } // don't display "null"
        label = `${label}<div title='${cleanHtmlTitle(_value)}' class="table-col mat-header-cell">${this.formatHeaderText(_key.toString())}</div>`;
      }
    });
    return label;
}

I suppose the issue could be that because the method above is of type: string, the html carets "<" and ">" are being encoded as &lt; and &gt; as part of the interpolated values, something the [innerHTML] directive does not do. I just need to understand how to resolve it so that I can move on with the refactor of this template.


Solution

  • The problem you are facing is a very common one considering that in a lot of scenarios, we intend to somehow inject our own HTML into the template, and it can be easily done via innerHTML attribute like you have demonstrated. Also, as you have correctly mentioned, this injected HTML is just plain HTML and wont support Angular magic since It's injected after the Angular Code Compilation.

    So what's the solution? The one that you propose simply fails because it is string interpolation (with double curly braces) and renders on the document whatever you mention. Dom Sanitizer won't help since that too is injecting HTML after compilation so no angular magic.

    One sensible solution I can think of is to create an angular component for you table header, provide it inputs that you are using to construct custom HTML and then pass all the input to Table Header Component (Via @Input()) and let it use it's template HTML depending on conditions arrived upon from inputs.

    @Component({
       selector:    'my-th',
       template: '<th [title]="Inp1" [ngClass]="{'some-class':true}"></th>',
    })
    export class TableHeadComponent {
        @Input() inp1;
        @Output() outpt;
        class: string = 'xyz';
    }
    

    And the parent component could then use it like

    <my-th [inp1]="inp" (outpt)="handle()"></my-th>