I am currently facing an issue while trying to parse and inject HTML into my application.
- I get a string from an API
- String is parsed, and some content is replaced by a tag at the service level. The new string looks like this: - [mention]{\"label\": \"Stack\"}[/mention] with some other text and a second [mention]{\"label\": \"Overflow\"}[/mention] with text
- Then the string is displayed by a component in the view - <div class="custom">{{ parsedString }}</div>
I tried multiple methods like using innerHTML with a sanitizeHTML pipe, and read some posts of people having this issue when adding router links to their HTML, but I seem to not understand properly how to use viewContainerRef in my case.
I saw this answer linked multiple times, but to me it's a mashed potato of code and I cannot get the logic behind it...
Update
So I ended up using a Pipe to parse the string and have a template using each part of the string to display a component if needed.
- My view component gets the string from an API
- The view component inserts the string in its template and use a pipe to parse it.
- The filter returns an array of strings which can be used in an *ngForloop
view.component.ts
<div *ngFor="let string of text | mentions">
    <ng-container *ngIf="string.label">
        <mention> <!-- We can use a component with dynamic HTML ! -->
            {{ string.label }}
        </mention>
    </ng-container>
    <ng-container *ngIf="!string.label">
        {{ string }}
    </ng-container>
</div>
mentions.pipe.ts
/* Angular */
import { Injectable, Pipe, PipeTransform } from '@angular/core';
@Pipe({
    name: 'mentions'
})
@Injectable()
export class MentionsPipe implements PipeTransform {
    tryParseJSON(string: string) {
        try {
            const json = JSON.parse(string);
            if (json && typeof json === 'object') {
                return json;
            }
        } catch (e) {}
        return string;
    };
    transform(value: string) {
        const regex = /\[mention\]([^[]*(?:\[(?!mention\]|\/mention\])[^[]*)*)\[\/mention\]/ig;
        return value
            .split(regex)
            .map((match: string) => {
                // The mention is a stringified JSON, so we want to parse it
                return this.tryParseJSON(match);
            });
    }
}
This may not be the best solution as it would mean using the pipe in an *ngFor loop each time we know that dynamic HTML will include mentions (in my case), but for now I am ok with this, until Angular provides a nice and secured way to detect and replace components in injected HTML.
 
    