Angular 默认对动态 HTML 采取不信任策略。即使你在模板中写了 [innerHTML],框架也不会直接把字符串塞进 DOM,而是先调用内置的安全清洗器做一次过滤。这个机制能拦截 <script> 标签、onclick 等事件处理器以及 javascript: 伪协议,但它也会把很多合法的内联样式或 class 属性过滤掉。理解这套清洗逻辑是安全渲染动态 HTML 的前提。

如果服务端返回的内容是由富文本编辑器生成的,可能包含 style 属性、iframe 或者 data URI,Angular 的默认清洗器会认为这些是危险标记并直接移除。此时你会发现页面展示效果和编辑器预览不一致。要解决这个问题,开发者通常会考虑用 DomSanitizer 把字符串标记为安全,但这会带来新的安全风险,需要谨慎评估来源。
Angular 的默认安全策略与 innerHTML 行为
Angular 在模板中通过插值或属性绑定时,会自动对值进行转义,防止恶意脚本注入。当你使用 [innerHTML] 时,很多人以为框架会直接设置元素的 innerHTML 属性,其实 Angular 会先经过内部的 Sanitizer。这个 Sanitizer 基于白名单机制,只允许安全的 HTML 标签和属性通过,例如 <p>、<strong>、<em>、<ul>、<li> 等,但会拒绝 <script>、<link>、<iframe> 等可能引入外部资源的标签,也会移除所有 on* 事件属性以及 javascript: 等危险协议。
下面这段代码展示了 Angular 对动态 HTML 的默认处理。组件中定义了一个包含危险内容的字符串,模板中使用 [innerHTML] 进行绑定。
import { Component } from '@angular/core';
@Component({
selector: 'app-raw-html',
standalone: true,
template: `<div [innerHTML]="htmlContent"></div>`,
})
export class RawHtmlComponent {
htmlContent = `<p style="color: red;">安全渲染</p><script>alert('xss')</script><a href="javascript:alert(1)">点我</a>`;
}
实际渲染后,<script> 标签会被直接移除,<a> 标签的 href 中 javascript: 协议会被替换为 unsafe:,style 属性也会被清除。也就是说 Angular 默认把内容当作不可信数据处理,这在大多数情况下是安全的,但对于需要完整展示富文本样式的场景就不够用了。
还需要注意,通过 ElementRef 直接操作原生 DOM 会绕过 Angular 的安全清洗。比如在组件中调用 this.elementRef.nativeElement.innerHTML = this.htmlContent,框架不会拦截,此时任何恶意脚本都会执行。所以除非有非常明确的理由,绝对不要使用 ElementRef 来设置 innerHTML,它相当于主动关闭了 Angular 的防线。
通过 DomSanitizer 显式信任 HTML
当后端返回的 HTML 已经经过服务端严格过滤,或者内容完全由内部可信系统生成时,可以使用 Angular 提供的 DomSanitizer 服务,将这些字符串显式标记为安全 HTML。DomSanitizer 包含多个方法,例如 bypassSecurityTrustHtml、bypassSecurityTrustStyle、bypassSecurityTrustResourceUrl 等,调用后会返回一个 SafeHtml 类型的包装对象,Angular 检测到 SafeHtml 后就会跳过清洗直接渲染。
要使用 DomSanitizer,需要在组件中注入该服务,然后调用 bypassSecurityTrustHtml 方法。下面的代码演示了基本用法。
import { Component } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Component({
selector: 'app-trusted-html',
standalone: true,
template: `<div [innerHTML]="trustedHtml"></div>`,
})
export class TrustedHtmlComponent {
trustedHtml: SafeHtml;
constructor(private sanitizer: DomSanitizer) {
const rawHtml = '<p style="color: blue;">受信任的内容</p>';
this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(rawHtml);
}
}
这样做可以让 style 属性保留下来,页面上能够看到蓝色文字。但必须清楚,一旦调用 bypassSecurityTrustHtml,Angular 不再对该字符串做任何过滤,如果 rawHtml 中包含用户可控制的部分,攻击者就可以注入 <script> 或恶意事件处理器,实现 XSS 攻击。因此这个操作只适用于绝对可信的内容源,并且最好在服务端已经做过一次白名单过滤。
在大型应用中,如果每个组件都手工调用 DomSanitizer,代码会变得重复,而且变更检测时可能反复执行 bypassSecurityTrustHtml。更合适的做法是创建一个纯管道,将清洗逻辑集中管理,并利用纯管道的缓存机制避免重复计算。下面的 SafeHtmlPipe 示例展示了这种封装方式。
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Pipe({
name: 'safeHtml',
standalone: true,
pure: true,
})
export class SafeHtmlPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(value: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
在模板中可以这样使用:
<div [innerHTML]="htmlContent | safeHtml"></div>
纯管道只有在输入值发生变化时才会重新计算,可以有效减少不必要的清洗调用。不过依然要牢记,管道内部并没有进行过滤,它只是把信任关系从组件转移到了管道,安全责任并没有消失。
更安全的替代方案与最佳实践
如果动态内容不仅仅是静态 HTML,而是包含交互逻辑或者复杂组件,渲染原始 HTML 往往不是最好的选择。Angular 提供了动态组件加载机制,可以基于组件类动态创建视图,例如使用 NgComponentOutlet 指令,或者通过 ViewContainerRef 编程式创建组件。这样做的好处是内容完全在 Angular 的组件体系内运行,根本不经过 HTML 字符串解析,也就不存在清洗和绕过清洗的问题。
下面是一个使用 NgComponentOutlet 的简单示例,假设已有一个受信任的组件 SomeTrustedComponent。
import { Component } from '@angular/core';
import { NgComponentOutlet } from '@angular/common';
@Component({
selector: 'app-dynamic-wrapper',
standalone: true,
imports: [NgComponentOutlet],
template: `<ng-container *ngComponentOutlet="dynamicComponent"></ng-container>`,
})
export class DynamicWrapperComponent {
dynamicComponent: any = SomeTrustedComponent;
}
当必须在 Angular 中渲染动态 HTML 时,建议在服务端或客户端引入专门的清理库,例如 DOMPurify,先对 HTML 做一次严格的过滤,只保留白名单标签和属性,然后再决定是否调用 bypassSecurityTrustHtml。同时配置内容安全策略 CSP 作为第二道防线,限制页面只能加载同源的脚本和样式。一个基础的 CSP 响应头示例如下:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';
需要特别避免的误区是:认为只要用了 DomSanitizer 就安全了。实际上 DomSanitizer 本身不做清洗,它只是告诉 Angular 跳过清洗。真正的安全必须建立在内容可信、服务端过滤、客户端过滤以及 CSP 限制的多层防护之上。不要信任任何来自用户输入或第三方接口的 HTML,即使你使用了安全管道。还要避免使用 ElementRef 直接写入 HTML,除非经过手动调用的第三方清理器。
最后形成一个决策清单:如果内容是纯文本,直接使用插值绑定;如果内容只包含简单格式化标签且来源不可信,使用 Angular 默认的 [innerHTML] 并接受清洗;如果内容来自可信源且需要保留样式,用 DomSanitizer 标记为安全,但必须保证源可信;如果内容包含交互或复杂结构,改用动态组件;如果需要最大程度保留样式又有不可信输入,先用 DOMPurify 过滤再安全渲染。按照这个顺序选择方案,可以在安全性和展示效果之间取得更好的平衡。
Angular动态HTMLDomSanitizer修改时间:2026-09-25 19:35:51