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, you will end up seeing the HTML code itself, like in your code editor!

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>Hello, World!</h1>";
}

The problem here is that Angular is not going to render this as a h1 tag!

Instead, the template character escaping mechanisms will kick in, and we will see the code itself displayed on the page, as if we were using a code editor:

<h1>Hello, World!</h1>

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.

So if we are trying to apply HTML code, Angular will try to escape the text according to HTML rules, if it's CSS it will escape the code according to CSS rules, etc.

So in the case above, Angular will detect that we are trying to inject text into an HTML context.

So any text character passed to the DOM is going to be escaped using HTML rules.

So, for example, the character "<" is going to escaped and turned into "<", etc.

This will then cause the character "<" to be displayed on the screen, instead of being interpreted as code.

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:

  1. Import the DomSanitizer service: Import DomSanitizer from the @angular/platform-browser package in the component where you need to bypass escaping.

  2. Inject DomSanitizer into your component's constructor: Add DomSanitizer to the component's constructor to make it available for use within the component.

  3. 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.