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
*ngFor
loop
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.