Display HTML in Angular Components

How to safely display any HTML in an Angular component without escaped or encoded characters.

Displaying HTML in an Angular component that’s stored in a template variable isn’t as straightforward as you’d expect. In order to properly store and display an HTML string in a component without issues, you’ll need to take one of two approaches depending on the tags and attributes in the HTML. If you’ve tried displaying HTML in your component with the standard template binding syntax ({{ var }}) you’ll end up with escaped HTML output. There’s a way around this and many of the other common headaches associated with Angular and storing HTML in a variable. Below are two explanations along with helpful examples of each approach that should hopefully make the process simple and easy for you.

Simple Scenarios with Basic HTML

In most cases you can get by with using the [innerHTML] attribute, but only if your content is considered safe from XSS. In this context, safe means it doesn’t contain any tags or attributes that can be used to inject JavaScript. Let’s look at a basic working example.

Component TypeScript

/**
 * Basic HTML Component Usage
 */
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'some-component',
  templateUrl: 'some-component.html',
})
export class SomeComponent implements OnInit {
  html: string;

  constructor() {}

  ngOnInit(): void {
    this.html = `<h2>We're currently undergoing essential maintenance, we'll be back online shortly.</h2>
    <p>If you have any questions please contact our customer services team via email at <a href="mailto:[email protected]?subject=Support Requested">[email protected]</a></p>`;
  }
}

Component Template

<section class="content" *ngIf="html" [innerHTML]="html"></section>

Rendered HTML Output

<section class="content">
   <h2>We're currently undergoing essential maintenance, we'll be back online shortly.</h2>
   <p>If you have any questions please contact our customer services team via email at <a href="mailto:[email protected]?subject=Support Requested">[email protected]</a></p>`;
</section>

If you’ve tried this approach and your HTML is mysteriously missing certain HTML tags, such as an <svg> element, then you’ll need to take a more advanced approach (see the second example below) to make sure Angular considered your HTML safe. (assuming it is)

Advanced Scenarios with Potentially Unsafe HTML

Angular includes a built-in DomSanitizer that helps protect your app from Cross Site Scripting Security bugs (XSS). This class works by sanitizing values so that any HTML text used in a component is safe to add to the DOM in a browser environment. If you’re trying to display HTML with any tag or property that can be used to inject or execute JavaScript, then it’s considered unsafe by the DomSanitizer.

To get around this you can use store the component DomSanitizers bypassSecurityTrustHtml method to mark your HTML as a SafeHtml type. It’s crucial that you understand why this exists: to make sure you don’t expose your app to XSS attacks. Below is a basic example of a component in action.

Component TypeScript

/**
 * Advanced HTML Component Usage
 */
import { Component, OnInit } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  selector: 'some-component',
  templateUrl: 'some-component.html',
})
export class SomeComponent implements OnInit {
  html: SafeHtml;

  constructor(private sanitizer: DomSanitizer) {}

  ngOnInit(): void {
    const template = `<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 512 512">
      <path d="M256 48C141.1 48 48 141.1 48 256s93.1 208 208 208 208-93.1 208-208S370.9 48 256 48zm17.2 109.6l-3.1 115.1c-.2 8.2-5.9 14.8-14.1 14.8s-13.9-6.6-14.1-14.8l-3.1-115.1c-.2-9.6 7.5-17.6 17.2-17.6 9.6 0 17.4 7.9 17.2 17.6zM256 354c-10.7 0-19.1-8.1-19.1-18.4s8.4-18.4 19.1-18.4c10.7 0 19.1 8.1 19.1 18.4S266.7 354 256 354z"/>
    </svg>
    <h2>We're currently undergoing essential maintenance, we'll be back online shortly.</h2>
    <p>If you have any questions please contact our customer services team via email at <a href="mailto:[email protected]?subject=Support Requested">[email protected]</a></p>`;

    this.html = this.sanitizer.bypassSecurityTrustHtml(template);
  }
}

Component Template

<section class="content" *ngIf="html" [innerHTML]="html"></section>

Rendered HTML Output

<section class="content">
   <svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 512 512">
      <path d="M256 48C141.1 48 48 141.1 48 256s93.1 208 208 208 208-93.1 208-208S370.9 48 256 48zm17.2 109.6l-3.1 115.1c-.2 8.2-5.9 14.8-14.1 14.8s-13.9-6.6-14.1-14.8l-3.1-115.1c-.2-9.6 7.5-17.6 17.2-17.6 9.6 0 17.4 7.9 17.2 17.6zM256 354c-10.7 0-19.1-8.1-19.1-18.4s8.4-18.4 19.1-18.4c10.7 0 19.1 8.1 19.1 18.4S266.7 354 256 354z"></path>
   </svg>
   <h2>We're currently undergoing essential maintenance, we'll be back online shortly.</h2>
   <p>If you have any questions please contact our customer services team via email at <a href="mailto:[email protected]?subject=Support Requested">[email protected]</a></p>`;
</section>

The Modern Angular Version (Standalone Components)

Both patterns above still work in current Angular, but the component boilerplate has changed since this post was written: standalone components are the default, inject() replaces constructor injection in most new code, and the @if control flow syntax replaces *ngIf. Here’s the advanced example rewritten the way you’d write it today:

import { Component, OnInit, inject } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({
  selector: 'some-component',
  template: `
    @if (html) {
      <section class="content" [innerHTML]="html"></section>
    }
  `,
})
export class SomeComponent implements OnInit {
  private sanitizer = inject(DomSanitizer);
  html!: SafeHtml;

  ngOnInit(): void {
    const template = `<svg>...</svg><h2>Maintenance in progress</h2>`;
    this.html = this.sanitizer.bypassSecurityTrustHtml(template);
  }
}

A Middle Ground: Sanitize Without Bypassing

One more option worth knowing about when the HTML comes from an API or CMS you don’t fully control: DomSanitizer.sanitize() with SecurityContext.HTML. Unlike bypassSecurityTrustHtml, this actually runs Angular’s sanitizer and strips dangerous tags and attributes while keeping the safe markup, rather than waving everything through:

import { SecurityContext, inject } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

const sanitizer = inject(DomSanitizer);
this.html = sanitizer.sanitize(SecurityContext.HTML, untrustedHtml) ?? '';

The rule of thumb: [innerHTML] alone for simple markup, sanitize() for third-party content you want cleaned rather than trusted, and bypassSecurityTrustHtml only for markup you authored yourself (like the SVG example above) where you can personally vouch for every tag in the string.

There you have it! These two approaches should cover any scenario where you need to embed HTML in an Angular component, regardless of the tags and properties it contains. It’s important to understand that when you use the advanced approach described above you bypass XSS checks, and you may be exposing your application to security issues. If you’re embedding HTML content from an API response in your Angular component template make sure that the HTML returned is safe and you should be alright.