There are a few common situations when developing a web application where we would like to apply some custom HTML directly to the page.
A typical case is for example when using a rich-text editor: let's say that we would like to display some HTML that we have saved on the database.
This sounds simple enough, but the reality is that the typical security features of a framework like Angular will make this task a bit more challenging than it sounds.
If you try to use the DOM innerHTML feature in an Angular application to apply some HTML to the page, this will not work as expected, the inline styles will be removed for example!
So in this guide, we will discuss some of the Angular built-in security measures that prevent this type of direct code injection, learn why they are in place, and show how we can still safely inject HTML content into the DOM if we really need to.
Table of Contents
- Understanding innerHTML in Angular
- The Problem with Direct innerHTML Use
- Security Implications of Unescaped HTML
- Angular's Sanitization and Security Measures
- Bypassing Angular's Escaping using DomSanitizer (With Caution)
- SafeHtml Pipe: A Safe Way to Inject HTML
Understanding innerHTML in Angular
The innerHTML
DOM property allows developers to dynamically inject HTML content into the DOM in general.
Notice that innerHTML
is not a specific Angular directive or anything, this is standard DOM functionality.
So let's assume that you are working on an Angular project, and you want to inject some HTML content into the DOM.
Your first instinct would be to use the innerHTML
property to try to achieve this:
<div [innerHTML]="htmlContent"></div>
export class AppComponent {
htmlContent = "<h1 style="color:red">Hello World</h1>";
}
The problem here is that Angular is not going to render this properly. The h1 tag will be rendered, but any inline styles typically applied in a rich text editor will be sanitized.
So the h1 tag will be displayed, but not in the color red.
And this is not what we were looking for!
So what is going on here?
The Problem with Direct innerHTML use in Angular
The reason why this doesn't work out of the box is due to the Angular built-in code injection defenses, also known as XSS defenses or Cross-Site Scripting defenses.
By default, Angular is going to assume that any text that we pass to the template is unsafe, and it will escape it properly according to the context where we are using the text.
This might seem surprising the first time that you see it, but it's actually a good thing.
This escaping mechanism protects our application from many different types of script injection security attacks.
It's great to have all these defenses in place, but sometimes there are good reasons to want to bypass them in certain cases, like in the case of our rich-text editor example.
Bypassing Angular Escaping with DomSanitizer (With Caution)
The DomSanitizer
service in Angular provides various methods to bypass security checks for trusted values.
Here's how you can use it:
-
Import the
DomSanitizer
service: Import DomSanitizer from the@angular/platform-browser
package in the component where you need to bypass escaping. -
Inject
DomSanitizer
into your component's constructor: AddDomSanitizer
to the component's constructor to make it available for use within the component. -
Use the appropriate sanitization method, depending on the type of code that you need to inject:
bypassSecurityTrustHtml
for HTML.bypassSecurityTrustStyle
for styles.bypassSecurityTrustScript
for script URLs.bypassSecurityTrustUrl
for URL/resource URLs.bypassSecurityTrustResourceUrl
for resource URLs like iframe sources.
Here is a complete example:
@Component({
selector: "app-unsafe-component",
template: `<div [innerHTML]="trustedHtml"></div>`,
})
export class UnsafeComponent {
trustedHtml: any;
constructor(private sanitizer: DomSanitizer) {
const unsafeHtml = "<h1>Hello World!</h1>";
// Bypassing Angular's HTML sanitizer
this.trustedHtml = this.sanitizer
.bypassSecurityTrustHtml(sanitizedHtml);
}
}
And this will now work as expected!
What we are doing here is telling Angular:
I'm sure that this text is safe, so you can skip the usual security measures.
It's important to note that when bypassing Angular's escaping, you take on the responsibility of ensuring that the content is actually safe to render as HTML.
As you can see, this process works, but it can be quite cumbersome...
So here is a way to make it much more convenient.
SafeHtml Pipe: A More Convenient Way to Inject HTML in an Angular Application
Let's then create a custom pipe that will sanitize HTML content passed to it.
It will return the sanitized HTML content to the caller to be injected into the DOM.
This pipe will internally use the DomSanitizer
, so that we don't have to use it directly in several places of our application:
@Pipe({
name: "safeHtml",
standalone: true,
})
export class SafeHtmlPipe {
constructor(private sanitizer: DomSanitizer) {}
transform(html) {
return this.sanitizer.bypassSecurityTrustHtml(html);
}
}
We can then use the pipe this way:
@Component({
standalone: true,
imports: [SafeHtmlPipe],
template: `
<div [innerHTML]="someHtmlContent | safeHtml">
</div> `,
})
export class TestComponent {}
And this will effectively inject the HTML into the DOM, by bypassing the typical Angular XSS defenses.
Remember, you are now responsible for the safety of the code that was just injected!
I hope that you enjoyed this post, to get notified when new similar posts like this are out, I invite you to subscribe to my newsletter:
You will also get up-to-date news on the Angular ecosystem.
And if you want a deep dive into all the features of Angular Core like Signals
and others, have a look at the Angular Core Deep Dive Course:
Summary
Angular has made sure to build an array of code injection defenses in its template rendering engine, which is awesome.
But sometimes, only in some very concrete practical scenarios, we really want the ability to inject some HTML directly into the DOM.
This usually has to do with the use of rich-text editors.
So for those rare cases only, we can leverage the DOM Sanitizer service.
The SafeHtml pipe that we provided makes it easy to conveniently inject code if you need to, but remember:
You need to make sure as much as possible that the code is actually safe to inject, so be mindful of this and only use this functionality when and if you really need it!
I hope this post helped, let me know in the comments below if you have any questions.